// Exercise the production dispatcher with real backing, including protection
// failures and concurrent operations. No alternate allocator implementation.
#include "../dm.h"
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdatomic.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

static int memory_mprotect(void *addr, size_t size, int prot);
static void *memory_mmap(void *addr, size_t size, int prot, int flags, int fd, off_t offset);
static _Noreturn void memory_abort(void);
static int memory_getpagesize(void) {
#ifdef TEST_PAGE_16K
    assert(getpagesize() <= 0x4000);
    return 0x4000;
#else
    return getpagesize();
#endif
}
#define getpagesize memory_getpagesize
#define mprotect memory_mprotect
#define mmap memory_mmap
#define abort memory_abort
#include "../hle_svc.c"
#undef abort
#undef mprotect
#undef mmap
#undef getpagesize

static module_t test_modules[2];
module_t *g_mod = test_modules;
int g_nmod;
struct dm_flags g_flag;
void hlecount_svc(int num) { (void)num; }
int svc_thread(int num, uint64_t *regs) { (void)num; (void)regs; return 0; }
void dm_backtrace(uint64_t fp) { (void)fp; }
void guest_clock_read_wait(void) {}
uint64_t guest_ns(void) { return 0; }
uint64_t guest_ticks(void) { return 0; }
uint64_t dm_entropy64(void) { return 0; }
int thr_limit_used(void) { return 0; }
int dm_ue_heap_owns(uint64_t a, uint64_t n) { (void)a; (void)n; return 0; } // No Unreal allocator is installed in this fixture.
// This fixture calls the SVC dispatcher directly, without rewritten guest gates.
void gate_obs_set(int id, void (*fn)(uint64_t *, int)) { (void)id; (void)fn; }
module_t *module_of(const void *p) {
    uintptr_t a = (uintptr_t)p;
    for (int i = 0; i < g_nmod; ++i) {
        uintptr_t b = (uintptr_t)g_mod[i].base;
        if (a >= b && a - b < g_mod[i].size) return &g_mod[i];
        if (g_mod[i].patch_off < 0 && a >= b + g_mod[i].patch_off && a < b) return &g_mod[i];
    }
    return NULL;
}

#ifdef TEST_REAL_DIRTY
#include "../../gfx/nvn/nvn_dirty.h"
static void (*write_prepare)(void *, size_t);
static int (*fault_hook)(void *, int, void *);
void dm_fault_hook_register(int (*fn)(void *, int, void *)) { fault_hook = fn; }
void dm_guest_write_prep_register(void (*fn)(void *, size_t)) { write_prepare = fn; }
static void memory_fault(int sig, siginfo_t *info, void *ctx) {
    if (fault_hook && fault_hook(info->si_addr, 1, ctx)) return;
    _exit(128 + sig);
}
#endif
static _Atomic unsigned prepares, protects;
static uintptr_t readonly_base;
static size_t readonly_size;
void dm_guest_write_prepare(void *addr, size_t size) {
    atomic_fetch_add(&prepares, 1);
#ifdef TEST_REAL_DIRTY
    if (write_prepare) write_prepare(addr, size);
#endif
    if (readonly_size && (uintptr_t)addr < readonly_base + readonly_size && readonly_base < (uintptr_t)addr + size) {
        assert(!mprotect((void *)readonly_base, readonly_size, PROT_READ | PROT_WRITE));
        readonly_size = 0;
    }
}

static int fail_protect, fail_mmap;
static uint64_t *failure_regs, failure_input[3];
static int64_t failure_mapped;
static size_t failure_heap;
static int failure_nmap;
static unsigned failure_prepares;
static int memory_mprotect(void *addr, size_t size, int prot) {
    size_t pg = memory_getpagesize();
    assert(!((uintptr_t)addr & (pg - 1)) && !(size & (pg - 1)) && size);
    atomic_fetch_add(&protects, 1);
    if (fail_protect) { errno = EACCES; return -1; }
    return mprotect(addr, size, prot);
}
static void *memory_mmap(void *addr, size_t size, int prot, int flags, int fd, off_t offset) {
    if (fail_mmap) { errno = ENOMEM; return MAP_FAILED; }
    return mmap(addr, size, prot, flags, fd, offset);
}
static _Noreturn void memory_abort(void) {
    // Only the production fatal path reaches this wrapper; assertion failures
    // cannot pass these checks. No guest return or metadata publication allowed.
    if (!fail_protect || errno != EACCES || !failure_regs ||
        memcmp(failure_regs, failure_input, sizeof failure_input) ||
        g_alias_mapped != failure_mapped || g_heap_cur != failure_heap ||
        g_nmap != failure_nmap || prepares != failure_prepares) _exit(78);
    _exit(77);
}
enum { P = 0x1000, BAD = 0xd401, OOM = 0xd001, LIMIT = 0x10801 };
static uintptr_t alias, stack, heap;
static uint64_t call(unsigned svc, uint64_t x0, uint64_t x1, uint64_t x2) {
    uint64_t r[31] = {x0, x1, x2}; svc_dispatch(svc, r, 0); return r[0];
}
static uint64_t pages(int commit, uintptr_t a, size_t n) { return call(commit ? 0x2c : 0x2d, a, n, 0); }
static uint64_t resize(size_t n) { return call(1, 0, n, 0); }
static uint64_t map(uintptr_t d, uintptr_t s, size_t n) { return call(4, d, s, n); }
static uint64_t unmap(uintptr_t d, uintptr_t s, size_t n) { return call(5, d, s, n); }
static uint64_t info(unsigned id) {
    uint64_t r[31] = {0, id}; svc_dispatch(0x29, r, 0); assert(!r[0]); return r[1];
}
typedef struct { uint64_t base, size; uint32_t state, attr, perm, ipc, device, reserved; } memory_info;
static memory_info query(uintptr_t a) {
    uint8_t out[57]; memset(out, 0xa7, sizeof out);
    uint64_t r[31] = {(uintptr_t)(out + 1), 0, a}; svc_dispatch(6, r, 0);
    assert(!r[0] && !r[1] && out[0] == 0xa7);
    for (unsigned i = 41; i < sizeof out; ++i) assert(out[i] == 0xa7);
    memory_info m; memcpy(&m, out + 1, sizeof m);
    assert(!m.ipc && !m.device && !m.reserved && m.size && a >= m.base && a - m.base < m.size);
    return m;
}
static void expect(uintptr_t a, uintptr_t base, size_t size, unsigned state, unsigned attr, unsigned perm) {
    memory_info m = query(a);
    assert(m.base == base && m.size == size && m.state == state && m.attr == attr && m.perm == perm);
}
static void fatal(unsigned svc, uint64_t x0, uint64_t x1, uint64_t x2) {
    fflush(NULL); pid_t pid = fork(); assert(pid >= 0);
    if (!pid) {
        alarm(5);
        uint64_t r[31] = {x0, x1, x2}; failure_regs = r;
        memcpy(failure_input, r, sizeof failure_input);
        failure_mapped = g_alias_mapped; failure_heap = g_heap_cur;
        // Releases first replace the mapping, then fall back to mprotect. Both
        // must fail to exercise the fatal path without publishing metadata.
        failure_nmap = g_nmap; failure_prepares = prepares; fail_protect = fail_mmap = 1;
        svc_dispatch(svc, r, 0); _exit(79);
    }
    int status; assert(waitpid(pid, &status, 0) == pid);
    assert(WIFEXITED(status) && WEXITSTATUS(status) == 77);
}

static void alias_lifecycle(void) {
    assert(!pages(1, alias + 4 * P, 4 * P));
    memset((void *)(alias + 4 * P), 0x53, 4 * P);
    assert(!pages(1, alias + 6 * P, 4 * P));
    assert(g_alias_mapped == 6 * P);
    expect(alias + 7 * P, alias + 4 * P, 6 * P, 5, 0, 3);
    expect((uintptr_t)g_as, (uintptr_t)g_as, heap - (uintptr_t)g_as, 0, 0, 0);
    expect(stack, heap, alias + 4 * P - heap, 0, 0, 0);
    expect(alias + 10 * P, alias + 10 * P, (uintptr_t)g_as + g_reserve - alias - 10 * P, 0, 0, 0);
    for (size_t i = 0; i < 4 * P; ++i) assert(((uint8_t *)alias)[4 * P + i] == 0x53);
    for (size_t i = 8 * P; i < 10 * P; ++i) assert(((uint8_t *)alias)[i] == 0);
    unsigned before = protects;
    assert(!pages(1, alias + 4 * P, 6 * P) && protects == before);
    assert(!pages(0, alias + 5 * P, P));
    expect(alias + 5 * P, alias + 5 * P, P, 0, 0, 0);
    expect(alias + 6 * P, alias + 6 * P, 4 * P, 5, 0, 3);
    assert(*(uint8_t *)(alias + 4 * P) == 0x53 && *(uint8_t *)(alias + 6 * P) == 0x53);
    assert(!pages(1, alias + 5 * P, P));
    for (size_t i = 0; i < P; ++i) assert(((uint8_t *)alias)[5 * P + i] == 0);
    assert(*(uint8_t *)(alias + 4 * P) == 0x53);
    assert(!pages(0, alias, 16 * P) && !pages(0, alias, 16 * P) && !g_alias_mapped);
    // First commit of a logical sibling after its host page was writable must
    // clear retained bytes, even though that logical page was never committed.
    size_t pg = memory_getpagesize();
    assert(!pages(1, alias, P));
    if (pg > P) {
        memset((void *)(alias + P), 0xa9, P);
        assert(!pages(1, alias + P, P));
        for (size_t i = 0; i < P; ++i) assert(((uint8_t *)alias)[P + i] == 0);
    }
    assert(!pages(0, alias, pg));
    assert(pages(1, alias, 0) == BAD && pages(1, alias + 1, P) == BAD);
    assert(pages(1, alias, P + 1) == BAD && pages(1, alias - P, P) == BAD);
    assert(pages(1, UINTPTR_MAX - P + 1, 2 * P) == BAD);
    assert(pages(1, alias + g_alias_cap - P, 2 * P) == BAD);
}

// Device-shared memory (an NVN memory pool's storage, hle_nvn.c) is not given back: UnmapPhysicalMemory or a shrinking SetHeapSize over any page of it fails with InvalidCurrentMemory and
// leaves every page and byte as it was, as on Horizon; once the pool is gone (each registration removed) both work again (PR #1616, Monster Jam Steel Titans 2)
static void device_shared_refused(void) {
    assert(!pages(1, alias, 16 * P));
    memset((void *)alias, 0x6d, 16 * P);
    int64_t mapped = g_alias_mapped;
    dm_device_share((void *)(alias + 4 * P), 4 * P, 1); dm_device_share((void *)(alias + 4 * P), 4 * P, 1);   // two pools over the same storage
    assert(pages(0, alias + 7 * P, 2 * P) == BAD && pages(0, alias, 16 * P) == BAD && pages(0, alias + 4 * P, 4 * P) == BAD);
    assert(g_alias_mapped == mapped);
    for (size_t i = 0; i < 16 * P; ++i) assert(((uint8_t *)alias)[i] == 0x6d);
    assert(!pages(0, alias + 12 * P, 4 * P) && !pages(0, alias, 2 * P));   // beside it: given back
    assert(!pages(1, alias + 2 * P, 2 * P) && !pages(1, alias + 8 * P, 2 * P));   // touching it on either side: still allowed
    dm_device_share((void *)(alias + 4 * P), 4 * P, 0);
    assert(pages(0, alias + 4 * P, P) == BAD);   // the other pool still holds it
    dm_device_share((void *)(alias + 4 * P), 4 * P, 0);
    assert(!pages(0, alias, 16 * P) && !g_alias_mapped);
    assert(!resize(4 * 1024 * 1024));
    dm_device_share((void *)(heap + 3 * 1024 * 1024), P, 1);
    assert(resize(2 * 1024 * 1024) == BAD && g_heap_cur == 4 * 1024 * 1024);
    dm_device_share((void *)(heap + 3 * 1024 * 1024), P, 0);
    dm_device_share((void *)(heap + 1024 * 1024), P, 1);
    assert(!resize(2 * 1024 * 1024) && g_heap_cur == 2 * 1024 * 1024);   // below the new end: left alone
    assert(resize(0) == BAD);
    dm_device_share((void *)(heap + 1024 * 1024), P, 0);
    assert(!resize(0));
}

static void ownership(void) {
    assert(!pages(1, alias, 16 * P));
    memset((void *)alias, 0x34, 16 * P);
    assert(!map(stack + P, alias + P, 2 * P));
    expect(alias + P, alias + P, 2 * P, 5, 1, 0);
    expect(alias, alias, P, 5, 0, 3);
    expect(alias + 3 * P, alias + 3 * P, 13 * P, 5, 0, 3);
    expect(stack + P, stack + P, 2 * P, 0xb, 0, 3);
    assert(map(stack + 8 * P, alias + 2 * P, P) == BAD);
    assert(map(stack + 2 * P, alias + 8 * P, P) == BAD);
    assert(pages(0, alias, 4 * P) == BAD);
    // SDK StandardAllocator extends committed windows across existing stacks.
    // Recommit must preserve the borrowed source and its authoritative copy.
    assert(!pages(1, alias, 4 * P));
    assert(!pages(0, alias, P));
    assert(!pages(1, alias, 4 * P)); // initializes a hole beside the borrowed source
    assert(!pages(1, alias + 15 * P, 2 * P));
    expect(alias + P, alias + P, 2 * P, 5, 1, 0);
    assert(*(uint8_t *)(alias + P) == 0x34);
    expect(UINTPTR_MAX, UINTPTR_MAX - P + 1, P, 0, 0, 0);
    assert(call(2, alias + P, P, 3) == BAD && call(3, alias + P, P, 0) == BAD);
    assert(unmap(stack + P, alias + P, P) == BAD);
    assert(unmap(stack + P, alias + 4 * P, 2 * P) == BAD);
    assert(!pages(0, alias, P) && !pages(1, alias, P));
    assert(*(uint8_t *)(alias + P) == 0x34);
    memset((void *)(stack + P), 0x76, 2 * P);
    readonly_base = alias; readonly_size = memory_getpagesize();
    assert(!mprotect((void *)readonly_base, readonly_size, PROT_READ));
    assert(!unmap(stack + P, alias + P, 2 * P) && !readonly_size);
    for (size_t i = P; i < 3 * P; ++i) assert(((uint8_t *)alias)[i] == 0x76);
    expect(alias + P, alias, 17 * P, 5, 0, 3);
    assert(!pages(0, alias, 17 * P));
    assert(map(stack, alias, P) == BAD);
    assert(map(stack + 1, alias, P) == 0xcc01 && map(stack, alias, 0) == 0xca01);
    assert(map(alias, alias, P) == 0xdc01 && map(stack, UINTPTR_MAX - P + 1, 2 * P) == BAD);
    assert(!resize(4 * 1024 * 1024));
    assert(!map(stack, heap + 3 * 1024 * 1024, P));
    assert(resize(2 * 1024 * 1024) == BAD && g_heap_cur == 4 * 1024 * 1024);
    assert(!resize(6 * 1024 * 1024));
    *(uint8_t *)stack = 0x91;
    assert(!unmap(stack, heap + 3 * 1024 * 1024, P));
    assert(*(uint8_t *)(heap + 3 * 1024 * 1024) == 0x91);
    assert(!resize(0));
}

static void module_sources(void) {
    uint8_t *b = mmap(NULL, 0x20000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    assert(b != MAP_FAILED);
    g_mod[0] = (module_t){.base=b, .size=0x20000, .text_end=0x2800, .ro_off=0x2800,
        .data_off=0x3000, .patch_off=0x1e000, .key_page_off=0x8000, .key_page_size=0x4000, .loaded=1};
    g_nmod = 1;
    assert(!mprotect(b, 0x4000, PROT_READ));
    assert(!mprotect(b + 0x8000, 0x4000, PROT_READ));
    assert(!mprotect(b + 0x1c000, 0x4000, PROT_READ));
    assert(map(stack, (uintptr_t)b + 0x3000, P) == BAD);
    assert(map(stack, (uintptr_t)b + 0x7000, 2 * P) == BAD);
    assert(map(stack, (uintptr_t)b + 0x1b000, 2 * P) == BAD);
    assert(!map(stack, (uintptr_t)b + 0x4000, P));
    expect((uintptr_t)b + 0x4000, (uintptr_t)b + 0x4000, P, 4, 1, 0);
    expect((uintptr_t)b + 0x8000, (uintptr_t)b + 0x8000, 0x4000, 4, 0, 1);
    *(uint8_t *)stack = 0x62;
    assert(!unmap(stack, (uintptr_t)b + 0x4000, P) && b[0x4000] == 0x62);
    g_mod[0].nro = 1;
    assert(!map(stack, (uintptr_t)b + 0x4000, P));
    assert(!svc_nro_deactivate(&g_mod[0]) && g_mod[0].loaded);
    assert(!unmap(stack, (uintptr_t)b + 0x4000, P));
    assert(svc_nro_deactivate(&g_mod[0]) && !g_mod[0].loaded);
    assert(map(stack, (uintptr_t)b + 0x4000, P) == BAD);
    g_nmod = 0; memset(g_mod, 0, sizeof test_modules); assert(!munmap(b, 0x20000));
}

// A module with the patch area before the base and a second one after the image (patch_nso.py TWO, EA SPORTS FC 27's 185.6 MB of text): data and bss end at the back area,
// which is reported as CodeData+RX and is no mapping source, like a patch area after the image
static void module_back_patch(void) {
    uint8_t *a = mmap(NULL, 0x30000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0), *b = a + 0x8000;
    assert(a != MAP_FAILED);
    g_mod[0] = (module_t){.base=b, .size=0x28000, .text_end=0x2800, .ro_off=0x2800, .data_off=0x3000, .patch_off=-0x8000, .patch_len=0x4000, .image_size=0x28000,
        .file_image_len=0x4000, .key_page_off=0x10000, .key_page_size=0x4000, .back_off=0x20000, .back_len=0x8000, .loaded=1};
    g_nmod = 1;
    assert(!mprotect(b, 0x4000, PROT_READ));
    assert(!mprotect(b + 0x10000, 0x4000, PROT_READ));
    assert(!mprotect(b + 0x20000, 0x8000, PROT_READ));
    expect((uintptr_t)b - 0x8000, (uintptr_t)b - 0x8000, 0x4000, 4, 0, 5);
    expect((uintptr_t)b + 0x4000, (uintptr_t)b + 0x4000, 0xc000, 4, 0, 3);
    expect((uintptr_t)b + 0x14000, (uintptr_t)b + 0x14000, 0xc000, 4, 0, 3);
    expect((uintptr_t)b + 0x20000, (uintptr_t)b + 0x20000, 0x8000, 4, 0, 5);
    expect((uintptr_t)b + 0x27000, (uintptr_t)b + 0x20000, 0x8000, 4, 0, 5);
    assert(map(stack, (uintptr_t)b + 0x1f000, 2 * P) == BAD);
    assert(map(stack, (uintptr_t)b + 0x20000, P) == BAD);
    assert(!map(stack, (uintptr_t)b + 0x14000, P));
    assert(!unmap(stack, (uintptr_t)b + 0x14000, P));
    g_nmod = 0; memset(g_mod, 0, sizeof test_modules); assert(!munmap(a, 0x30000));
}

// Apple has no pthread_barrier. The gate makes races deterministic at entry,
// without assumptions about which operation wins the memory-state lock.
typedef struct { pthread_mutex_t mu; pthread_cond_t cv; unsigned waiting; } gate;
static void gate_wait(gate *g) {
    assert(!pthread_mutex_lock(&g->mu));
    if (++g->waiting == 2) assert(!pthread_cond_broadcast(&g->cv));
    else while (g->waiting < 2) assert(!pthread_cond_wait(&g->cv, &g->mu));
    assert(!pthread_mutex_unlock(&g->mu));
}
typedef struct { gate *start; unsigned svc; uint64_t a, b, c, result; } task;
static void *compete(void *arg) {
    task *t = arg; gate_wait(t->start); t->result = call(t->svc, t->a, t->b, t->c); return NULL;
}
static void race(task *a, task *b) {
    gate g = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};
    a->start = b->start = &g; pthread_t ta, tb;
    assert(!pthread_create(&ta, NULL, compete, a) && !pthread_create(&tb, NULL, compete, b));
    assert(!pthread_join(ta, NULL) && !pthread_join(tb, NULL));
    assert(!pthread_cond_destroy(&g.cv) && !pthread_mutex_destroy(&g.mu));
}
static void concurrency(void) {
    for (unsigned i = 0; i < 40; ++i) {
        task h = {.svc=1, .b=8 * 1024 * 1024}, p = {.svc=0x2c, .a=alias, .b=8 * 1024 * 1024};
        race(&h, &p);
        if (DM_GAME_MEM_MAP_LIMIT) assert((!h.result && p.result == LIMIT) || (h.result == LIMIT && !p.result));
        else assert(!h.result && !p.result);
        assert(info(I_UsedMem) == DM_GAME_MEM_USED + g_heap_cur + (DM_GAME_MEM_MAP_LIMIT ? g_alias_mapped : 0));
        assert(!pages(0, alias, p.b) && !resize(0));
        assert(!pages(1, alias, P));
        task m = {.svc=4, .a=stack, .b=alias, .c=P}, d = {.svc=0x2d, .a=alias, .b=P};
        race(&m, &d);
        assert((!m.result && d.result == BAD) || (m.result == BAD && !d.result));
        if (!m.result) assert(!unmap(stack, alias, P));
        assert(!pages(0, alias, P));
        assert(!resize(4 * 1024 * 1024));
        m.b = heap + 3 * 1024 * 1024; h.b = 2 * 1024 * 1024;
        race(&m, &h);
        assert((!m.result && h.result == BAD) || (m.result == BAD && !h.result));
        if (!m.result) assert(!unmap(stack, m.b, P));
        assert(!resize(0));
        assert(!pages(1, alias, 2 * P));
        m.b = alias;
        task other = {.svc=4, .a=stack + 0x4000, .b=alias, .c=P};
        race(&m, &other);
        assert((!m.result && other.result == BAD) || (m.result == BAD && !other.result));
        assert(!unmap(m.result ? other.a : m.a, alias, P));
        other.a = stack; other.b = alias + P;
        race(&m, &other);
        assert((!m.result && other.result == BAD) || (m.result == BAD && !other.result));
        assert(!unmap(stack, m.result ? other.b : m.b, P));
        assert(!pages(0, alias, 2 * P));
    }
}
static void fragmented_queries(void) {
    // Independent byte-sized model checks word boundaries and free runs across
    // the alias partition. Preserve every live page's marker on overlapping maps.
    enum { N = 160 };
    unsigned char live[N] = {0}; uint32_t rng = 0x13579b;
    for (unsigned step = 0; step < 1000; ++step) {
        rng = rng * 1664525 + 1013904223; unsigned first = rng % N;
        rng = rng * 1664525 + 1013904223; unsigned n = 1 + rng % (N - first);
        int commit = (rng >> 20) & 1;
        assert(!pages(commit, alias + first * P, n * P));
        for (unsigned i = first; i < first + n; ++i) {
            if (commit) {
                assert(*(uint8_t *)(alias + i * P) == (live[i] ? 0x57 : 0));
                *(uint8_t *)(alias + i * P) = 0x57;
            }
            live[i] = commit;
        }
        unsigned total = 0;
        for (unsigned i = 0; i < N; ++i) {
            total += live[i];
            unsigned lo = i, hi = i + 1;
            while (lo && live[lo - 1] == live[i]) --lo;
            while (hi < N && live[hi] == live[i]) ++hi;
            uintptr_t b = !live[i] && !lo ? heap : alias + lo * P;
            uintptr_t e = !live[i] && hi == N ? (uintptr_t)g_as + g_reserve : alias + hi * P;
            expect(alias + i * P, b, e - b, live[i] ? 5 : 0, 0, live[i] ? 3 : 0);
        }
        assert(g_alias_mapped == total * P);
    }
    assert(!pages(0, alias, N * P));
}
static void lazy_commit(void) {
    size_t n = DM_GAME_APP_POOL - DM_GAME_MEM_USED, pg = memory_getpagesize();
    char *resident = malloc(n / pg); assert(resident);
    assert(!pages(1, alias, n));
    assert(!mincore((void *)alias, n, resident));
    size_t count = 0;
    for (size_t i = 0; i < n / pg; ++i) count += !!(resident[i] & MINCORE_INCORE);
    assert(count < 32); // metadata scans must not fault the anonymous backing in
    ((uint8_t *)alias)[0] = 0x98;
    assert(!pages(0, alias, pg) && !pages(1, alias, pg));
    for (size_t i = 0; i < pg; ++i) assert(((uint8_t *)alias)[i] == 0);
    assert(!pages(0, alias, n)); free(resident);
}
static void metadata_limit(void) {
    assert(!pages(1, alias, (MAX_MAP + 1) * P));
    for (unsigned i = 0; i < MAX_MAP; ++i) assert(!map(stack + i * P, alias + i * P, P));
    unsigned before = protects;
    assert(map(stack + MAX_MAP * P, alias + MAX_MAP * P, P) == 0xce01);
    assert(protects == before && g_nmap == MAX_MAP);
    expect(alias + MAX_MAP * P, alias + MAX_MAP * P, P, 5, 0, 3);
    // Releasing one destination makes its metadata available again.
    assert(!unmap(stack + 123 * P, alias + 123 * P, P));
    assert(!map(stack + 123 * P, alias + MAX_MAP * P, P));
    assert(protects == before + 1);
    for (unsigned i = 0; i < MAX_MAP; ++i)
        assert(!unmap(stack + i * P, alias + (i == 123 ? MAX_MAP : i) * P, P));
    assert(!pages(0, alias, (MAX_MAP + 1) * P));
}
#ifdef TEST_REAL_DIRTY
static _Atomic int dirty_done;
static void *protect_worker(void *unused) {
    (void)unused;
    while (!atomic_load(&dirty_done)) nvn_dirty_protect((void *)alias, P);
    return NULL;
}
static void dirty_integration(void) {
    struct sigaction sa = {.sa_sigaction=memory_fault, .sa_flags=SA_SIGINFO};
    sigemptyset(&sa.sa_mask);
    assert(!sigaction(SIGBUS, &sa, NULL) && !sigaction(SIGSEGV, &sa, NULL));
    size_t pg = memory_getpagesize(); assert(pg > P);
    assert(!pages(1, alias, 2 * pg));
    nvn_dirty_add_range((void *)alias, P);
    nvn_dirty_add_range((void *)(alias + P), P);
    *(uint8_t *)alias = 0x29;
    for (unsigned i = 0; i < 20; ++i) {
        nvn_dirty_protect((void *)alias, P);
        nvn_dirty_protect((void *)(alias + P), P);
        uint64_t stamp = nvn_dirty_now();
        assert(!pages(0, alias + P, P) && !pages(1, alias + P, P));
        assert(nvn_dirty_since((void *)alias, P, stamp));
        assert(nvn_dirty_since((void *)(alias + P), P, stamp));
        assert(*(uint8_t *)alias == 0x29 && *(uint8_t *)(alias + P) == 0);
        assert(!map(stack, alias, P));
        *(uint8_t *)stack = 0x29;
        nvn_dirty_protect((void *)alias, P); stamp = nvn_dirty_now();
        assert(!unmap(stack, alias, P));
        assert(nvn_dirty_since((void *)alias, P, stamp));
    }
    pthread_t thread; assert(!pthread_create(&thread, NULL, protect_worker, NULL));
    for (unsigned i = 0; i < 1000; ++i) {
        assert(!pages(0, alias + P, P) && !pages(1, alias + P, P));
        assert(!map(stack, alias, P)); *(uint8_t *)stack = 0x29;
        assert(!unmap(stack, alias, P));
        assert(*(uint8_t *)alias == 0x29 && *(uint8_t *)(alias + P) == 0);
    }
    atomic_store(&dirty_done, 1); assert(!pthread_join(thread, NULL));
    nvn_dirty_remove_range((void *)alias); nvn_dirty_remove_range((void *)(alias + P));
    assert(!pages(0, alias, 2 * pg));
    puts("real GPU dirty tracking: shared-page recommit, copyback and concurrent protection passed");
}
#endif
static void failures(void) {
    fatal(0x2c, alias, 0x4000, 0);
    assert(!pages(1, alias, 0x4000));
    fatal(0x2d, alias, 0x4000, 0);
    fatal(4, stack, alias, P);
    assert(!pages(0, alias, 0x4000));
    fatal(1, 0, 0x200000, 0);
    assert(!resize(0x200000)); fatal(1, 0, 0, 0); assert(!resize(0));
}
static void release_fallback(void) {
    size_t pg = memory_getpagesize();
    assert(!pages(1, alias, pg));
    memset((void *)alias, 0x53, pg);
    fail_mmap = 1;
    assert(!pages(0, alias, pg));
    assert(!g_alias_mapped);
    expect(alias, heap, (uintptr_t)g_as + g_reserve - heap, 0, 0, 0);
    fail_mmap = 0;
    // The fallback retained the old backing: recommit must still clear it.
    assert(!pages(1, alias, pg));
    for (size_t i = 0; i < pg; ++i) assert(((uint8_t *)alias)[i] == 0);
    assert(!pages(0, alias, pg));
    assert(!resize(0x200000));
    fail_mmap = 1;
    assert(!resize(0) && !g_heap_cur);
    fail_mmap = 0;
}
// A heap given back reads 0 when it grows again: a fresh mapping in anonymous memory, and SetHeapSize's zeroing with the guest in files (DM_GUEST_FILE), where the pages keep their bytes
// The rule that puts the guest in files: the switch when it is set, otherwise a pool over a known room to the memory limit
static void file_rule(void) {
    const uint64_t G = 1ull << 30, pool9 = 9 * G, pool3 = 0xCD500000;
    assert(guest_file_wanted(NULL, pool9, 8 * G) && guest_file_wanted("", pool9, 6 * G));   // Mario Kart World's pool on an 8 and a 6 GiB limit
    assert(!guest_file_wanted(NULL, pool9, 12 * G) && !guest_file_wanted(NULL, pool3, 6 * G));   // it fits an iPad's 12 GiB; a Switch game's pool fits 6
    assert(!guest_file_wanted(NULL, pool9, 0));   // no limit known: the Mac
    assert(!guest_file_wanted("0", pool9, 8 * G) && guest_file_wanted("1", pool3, 12 * G) && guest_file_wanted("1", pool3, 0));
}
static void heap_regrow(void) {
    assert(!resize(4 * 1024 * 1024));
    memset((void *)(heap + 3 * 1024 * 1024), 0x5a, P);
    assert(!resize(2 * 1024 * 1024) && !resize(4 * 1024 * 1024));
    for (size_t i = 0; i < P; ++i) assert(((uint8_t *)heap)[3 * 1024 * 1024 + i] == 0);
    assert(!resize(0));
}
// Exercise the real host helper through SDK-shaped test entry points. Its stack
// allocation must reach MapMemory as guest backing and outlive SDK copyback.
#include "../hle_guestthread.c"
static void (*helper_entry)(void *);
static void *helper_arg, *helper_stack;
static size_t helper_size;
static int helper_started, helper_destroyed, helper_freed;
void dm_thread_mark_host(void) {}
uint32_t thr_cur_handle(void) { return 0x1001; }
static void *helper_malloc(size_t n) {
    assert(n < 0x200000); assert(!resize(0x200000));
    return (void *)(heap + 32); // unaligned allocation, like SDK malloc
}
static void helper_free(void *p) {
    assert(p == (void *)(heap + 32) && helper_destroyed);
    assert(*(uint8_t *)helper_stack == 0x87); // DestroyThread already copied back
    helper_freed = 1; assert(!resize(0));
}
static int helper_create(void *tt, void (*fn)(void *), void *arg, void *source, size_t n, int prio) {
    (void)tt; (void)prio; helper_entry = fn; helper_arg = arg; helper_stack = source; helper_size = n;
    return (int)map(stack, (uintptr_t)source, n);
}
static void helper_start(void *tt) { (void)tt; helper_entry(helper_arg); helper_started = 1; }
static void helper_wait(void *tt) { (void)tt; assert(helper_started); }
static void helper_destroy(void *tt) {
    (void)tt; assert(!unmap(stack, (uintptr_t)helper_stack, helper_size)); helper_destroyed = 1;
}
void *guest_sym(const char *name) {
    if (!strcmp(name, "malloc")) return helper_malloc;
    if (!strcmp(name, "free")) return helper_free;
    if (!strcmp(name, "_ZN2nn2os12CreateThreadEPNS0_10ThreadTypeEPFvPvES3_S3_mi")) return helper_create;
    if (!strcmp(name, "_ZN2nn2os11StartThreadEPNS0_10ThreadTypeE")) return helper_start;
    if (!strcmp(name, "_ZN2nn2os10WaitThreadEPNS0_10ThreadTypeE")) return helper_wait;
    if (!strcmp(name, "_ZN2nn2os13DestroyThreadEPNS0_10ThreadTypeE")) return helper_destroy;
    assert(0); return NULL;
}
static void helper_body(void *arg) { assert(arg == &helper_started); *(uint8_t *)stack = 0x87; }
static void guest_helper(void) {
    void *tt = dm_guest_thread_spawn(helper_body, &helper_started, 9, 0x10000);
    assert(helper_started && !helper_destroyed && !helper_freed);
    assert(resize(0) == BAD);
    dm_guest_thread_join(tt); assert(helper_freed && !g_nmap && !g_heap_cur);
}
static void *init_worker(void *arg) { *(uint64_t *)arg = info(I_AliasAddr); return NULL; }
// The block: 256 MB in front, the heap region, the stack region, the alias region up to its end. The heap and stack sizes are the profile's, whatever the alias region's size
static void layout_order(void) {
    assert(info(I_HeapAddr) == (uint64_t)(uintptr_t)g_as + 0x10000000);
    assert(info(I_HeapSize) == DM_GAME_MEM_RESERVE - DM_GAME_MEM_ALIAS - DM_GAME_MEM_STACK - 0x10000000);
    assert(info(I_StackAddr) == info(I_HeapAddr) + info(I_HeapSize) && info(I_StackSize) == DM_GAME_MEM_STACK);
    assert(info(I_AliasAddr) == info(I_StackAddr) + info(I_StackSize));
    assert(info(I_AliasAddr) + info(I_AliasSize) == info(I_AslrAddr) + info(I_AslrSize));
    assert(info(I_AslrAddr) == (uint64_t)(uintptr_t)g_as && info(I_AslrSize) == g_reserve);
}
int main(int argc, char **argv) {
    if (argc == 2 && !strcmp(argv[1], "--heap-probe")) {
        mem_init();
        assert(resize(g_heap_cap + 0x200000) == OOM);
        g_nmod = 1; g_mod[0].size = 0xa00000;
        assert(resize(0x800000) == LIMIT);
        return 0;
    }
    if (argc == 2 && !strcmp(argv[1], "--alias-probe")) {   // DM_ALIAS_SIZE sets the alias region; the heap and stacks stay as the profile has them
        mem_init();
        assert(info(I_AliasSize) == 0x10000000);
        layout_order();
        return 0;
    }
    pthread_t threads[8]; uint64_t addresses[8];
    for (unsigned i = 0; i < 8; ++i) assert(!pthread_create(&threads[i], NULL, init_worker, &addresses[i]));
    for (unsigned i = 0; i < 8; ++i) { assert(!pthread_join(threads[i], NULL)); assert(addresses[i] == addresses[0]); }
    alias = addresses[0]; stack = info(I_StackAddr); heap = info(I_HeapAddr);
#if !TARGET_OS_IPHONE
    if (!getenv("DM_ALIAS_SIZE")) assert(info(I_AliasSize) == SWITCH_ALIAS);   // the Mac's alias region is a Switch's, whatever the profile can give an iPhone; DM_ALIAS_SIZE=<mem_alias> runs these tests on an iPhone's
#endif
    layout_order();
    lazy_commit(); alias_lifecycle(); device_shared_refused(); ownership(); module_sources(); module_back_patch(); failures(); release_fallback(); file_rule(); heap_regrow(); concurrency(); fragmented_queries();
    guest_helper();
    if (!DM_GAME_MEM_MAP_LIMIT) metadata_limit();
#ifdef TEST_REAL_DIRTY
    dirty_integration();
#endif
    assert(!g_nmap && !g_alias_mapped && !g_heap_cur);
    assert(info(I_UsedMem) == DM_GAME_MEM_USED);
    // Offline NRO copies are not extra guest memory: the game allocates their buffers in its heap. Ordinary modules' large bss still counts.
    g_nmod = 2;
    g_mod[0] = (module_t){.size=0x10000000, .file_image_len=0x100000, .patch_off=-0x2000000};
    g_mod[1] = (module_t){.size=0x160000000, .file_image_len=0x100000, .patch_off=-0x2000000, .nro=1};
    assert(info(I_UsedMem) == DM_GAME_MEM_USED + 0xff00000);
    assert(info(I_UsedNonSys) == DM_GAME_MEM_USED + 0xff00000);
    g_mod[0].back_len = 0x40000;   // a back patch area (patch_nso.py TWO) is code from the file, not bss
    assert(info(I_UsedMem) == DM_GAME_MEM_USED + 0xfec0000);
    g_mod[0].back_len = 0;
    assert(image_bytes() == 0x10000000);
    g_nmod = 0;
    free(g_alias_bits); free(g_alias_exposed); assert(!munmap(g_as, g_reserve));
    printf("memory SVCs passed (map limit=%d, host page=%d, guest files %d)\n", DM_GAME_MEM_MAP_LIMIT, memory_getpagesize(), g_gf_n);
    return 0;
}
