//! The kernel's page tables and virtual memory manager.
//!
//! Builds our own 4-level page tables and switches CR3 onto them, replacing the
//! firmware's. Unlike the earlier bootstrap this maps with real permissions:
//! RAM is identity-mapped read-write + no-execute, the kernel's own segments get
//! their ELF permissions (code R+X, rodata R, data R+W+NX), and page 0 is left
//! unmapped as a null guard. It also exposes map/unmap for on-demand mapping,
//! which the kernel heap will build on.
//!
//! The physmap (the permanent window onto all physical RAM) is built with 2 MiB
//! huge pages wherever the range is 2 MiB-aligned, falling back to 4 KiB for the
//! unaligned edges. On a big machine that is the difference between ~16.7M page-
//! table entries (128 MiB of tables) and ~32K — it makes both the build and the
//! footprint scale sanely with RAM. Everything else (kernel segments, heap, user
//! space, on-demand MMIO) stays 4 KiB: precise, and the table memory is
//! negligible there.

const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const io = @import("io.zig");

const page_size = abi.page_size;

// Page-table entry bits.
const present: u64 = 1 << 0;
const writable: u64 = 1 << 1;
const user: u64 = 1 << 2; // U/S: accessible from ring 3 (must be set at every level)
const pwt: u64 = 1 << 3; // page write-through
const pcd: u64 = 1 << 4; // page cache disable (with PWT: strong-uncacheable under the default PAT)
const page_size_bit: u64 = 1 << 7; // PS: this PDPT/PD entry is a 1 GiB/2 MiB leaf, not a pointer to the next table
const device_grant: u64 = 1 << 9; // available bit: this leaf maps device MMIO, not RAM — do not reclaim
const no_execute: u64 = 1 << 63;
const address_mask: u64 = 0x000F_FFFF_FFFF_F000;

// The PAT-index bit. In a 4 KiB PTE it is bit 7; in a huge leaf (2 MiB PDE / 1 GiB
// PDPTE) bit 7 is PS, so the PAT bit moves to bit 12. With PCD=PWT=0 this selects
// PAT entry 4, which `setupPat` programs to write-combining (see mapRangePhysmap).
const pte_pat: u64 = 1 << 7;
const huge_pat: u64 = 1 << 12;
const ia32_pat: u32 = 0x277;

/// The physmap's page size for 2 MiB-aligned RAM: one PD leaf covers this instead
/// of 512 PT entries. 4 KiB pages fill the unaligned edges (see mapRangePhysmap).
const huge_page_size: u64 = 2 << 20; // 2 MiB

// ELF segment flags (p_flags).
const pf_x: u32 = 1;
const pf_w: u32 = 2;

// State kept after init so map()/unmap() can serve later callers (e.g. the heap).
var kernel_pml4: u64 = 0;
var alloc_frame: *const fn () ?u64 = undefined;
var free_frame: *const fn (u64) void = undefined; // for tearing down address spaces

/// Set once the kernel is running on its own tables (past the CR3 load in
/// `init`). Before that, the kernel reaches page-table frames through the
/// *loader's* bootstrap physmap, which only covers the low 4 GiB — so every
/// frame allocated for a table during that window must be below 4 GiB. Both the
/// frame allocator and this code scan from low addresses up, so it holds
/// naturally; the assertion in `allocTable` makes a violation loud rather than
/// a silent fault. After the switch the kernel's own physmap covers all RAM.
var on_own_tables = false;

/// Set at the end of `init`. Guards against a new *higher-half* PML4 entry being
/// created afterward: the kernel half is pre-populated at init and then shared
/// by copying PML4[256..512) into every process address space (M3), so a late
/// top-half entry would be invisible to already-created address spaces.
var init_done = false;

const bootstrap_physmap_limit: u64 = 4 << 30;

/// Dereference a page-table frame by its physical address, via the physmap.
/// This is the single hinge for the higher-half move: page tables hold physical
/// frame addresses (pmm gives out physical frames, and CR3/PTEs must be
/// physical), but the kernel reaches them at `physmap_base + physical`. Valid under
/// both the loader's bootstrap tables and the kernel's own, which share the
/// physmap base.
fn tableAt(physical: u64) *[512]u64 {
    return @ptrFromInt(boot_handoff.physicalToVirtual(physical));
}

fn allocTable() u64 {
    const frame = alloc_frame() orelse @panic("paging: out of memory building page tables");
    if (!on_own_tables and frame >= bootstrap_physmap_limit)
        @panic("paging: table frame above the 4 GiB bootstrap physmap");
    @memset(tableAt(frame)[0..], 0);
    return frame;
}

/// Return the table an entry points at, creating it if empty. Intermediate
/// entries are writable and executable so the leaf's bits govern (a page is
/// writable only if every level is; non-executable if any level is).
fn descend(entry: *u64) u64 {
    if (entry.* & present != 0) {
        // A present-but-huge entry is a leaf, not a table: descending would read
        // its 2 MiB/1 GiB data frame as a page table and corrupt RAM. This only
        // fires on a bug — a 4 KiB map landing inside a physmap huge page — and a
        // loud panic beats silent corruption. (The physmap and the 4 KiB regions
        // live in disjoint PML4 slots, so it should never happen.)
        if (entry.* & page_size_bit != 0) @panic("paging: descend through a huge-page leaf");
        return entry.* & address_mask;
    }
    const frame = allocTable();
    entry.* = frame | present | writable;
    return frame;
}

/// Map one 4 KiB page `virtual` -> `physical` with `flags` (present is added).
fn mapPage(pml4: u64, virtual: u64, physical: u64, flags: u64) void {
    const pml4e = &tableAt(pml4)[(virtual >> 39) & 0x1FF];
    // The kernel half is fixed after init: every top-half PML4 entry is
    // pre-created so address spaces can share it by copying these slots. A new
    // one here would be invisible to address spaces already made.
    if (init_done and (virtual >> 63) == 1 and pml4e.* & present == 0)
        @panic("paging: new higher-half PML4 entry after init");
    const pdpt = descend(pml4e);
    const pdpte = &tableAt(pdpt)[(virtual >> 30) & 0x1FF];
    const pd = descend(pdpte);
    const pde = &tableAt(pd)[(virtual >> 21) & 0x1FF];
    const pt = descend(pde);
    tableAt(pt)[(virtual >> 12) & 0x1FF] = (physical & address_mask) | flags | present;
}

/// Map one 2 MiB huge page `virtual` -> `physical` with `flags` — a leaf at the PD
/// level (PS bit set), with no PT beneath it. Both addresses must be 2 MiB-aligned.
/// One of these replaces 512 `mapPage`s (and the PT frame they'd need).
fn mapHugePage(pml4: u64, virtual: u64, physical: u64, flags: u64) void {
    const pml4e = &tableAt(pml4)[(virtual >> 39) & 0x1FF];
    if (init_done and (virtual >> 63) == 1 and pml4e.* & present == 0)
        @panic("paging: new higher-half PML4 entry after init");
    const pdpt = descend(pml4e);
    const pdpte = &tableAt(pdpt)[(virtual >> 30) & 0x1FF];
    const pd = descend(pdpte);
    tableAt(pd)[(virtual >> 21) & 0x1FF] = (physical & address_mask) | flags | present | page_size_bit;
}

/// Map [physical_base, physical_base+len) into the physmap (at physicalToVirtual(physical)) with
/// `flags`, rounded out to whole pages. This is how the kernel keeps a permanent
/// window onto physical memory once the low identity map goes away. The 2 MiB-
/// aligned interior is mapped with huge pages; the unaligned head/tail with 4 KiB.
/// `write_combining` selects the WC memory type (setupPat's PAT entry 4) via the
/// PAT bit — bit 7 in a 4 KiB PTE, bit 12 in a huge leaf — for the framebuffer.
fn mapRangePhysmap(pml4: u64, physical_base: u64, len: u64, flags: u64, write_combining: bool) void {
    const pte_flags = if (write_combining) flags | pte_pat else flags;
    const huge_flags = if (write_combining) flags | huge_pat else flags;
    var address = physical_base & ~@as(u64, page_size - 1);
    const end = physical_base + len;
    // Head: 4 KiB pages up to the next 2 MiB boundary.
    while (address < end and address & (huge_page_size - 1) != 0) : (address += page_size)
        mapPage(pml4, boot_handoff.physicalToVirtual(address), address, pte_flags);
    // Interior: 2 MiB huge pages while a whole one still fits.
    while (address + huge_page_size <= end) : (address += huge_page_size)
        mapHugePage(pml4, boot_handoff.physicalToVirtual(address), address, huge_flags);
    // Tail: 4 KiB pages for whatever is left.
    while (address < end) : (address += page_size)
        mapPage(pml4, boot_handoff.physicalToVirtual(address), address, pte_flags);
}

fn regions(mm: boot_handoff.MemoryMap) []const boot_handoff.MemoryRegion {
    return @as([*]const boot_handoff.MemoryRegion, @ptrFromInt(boot_handoff.physicalToVirtual(mm.regions)))[0..mm.len];
}

/// Enable the NX bit in the page-table format (EFER.NXE). Must happen before we
/// load a CR3 whose entries set the NX bit, or those bits are reserved and fault.
fn enableNx() void {
    const efer_msr = 0xC0000080;
    io.wrmsr(efer_msr, io.rdmsr(efer_msr) | (1 << 11));
}

/// Program this core's PAT so entry 4 (selected by the PAT bit with PCD=PWT=0) is
/// **write-combining**, leaving the other seven at their reset types. Nothing else
/// in danos sets the PAT bit, so this changes no existing mapping — it only gives
/// the framebuffer a write-combining type, which turns its full-screen clear from
/// glacial (uncached writes to a GPU BAR, the real-hardware default via MTRRs) into
/// a batched burst. Must run on **every** core (PAT is per-logical-processor) — the
/// framebuffer mapping lives in the shared kernel half, so a core with the reset
/// PAT would see it as write-back and alias. Called from `init` (BSP) and each AP.
pub fn setupPat() void {
    // Reset PAT is PA0=WB PA1=WT PA2=UC- PA3=UC PA4=WB PA5=WT PA6=UC- PA7=UC; flip
    // PA4 from WB (0x06) to WC (0x01). Type codes: UC=0 WC=1 WT=4 WP=5 WB=6 UC-=7.
    io.wrmsr(ia32_pat, 0x0007_0401_0007_0406);
}

/// Build the address space and switch onto it.
pub fn init(allocFrame: *const fn () ?u64, freeFrame: *const fn (u64) void, boot_information: *const boot_handoff.BootInformation) void {
    alloc_frame = allocFrame;
    free_frame = freeFrame;
    enableNx();
    setupPat(); // BSP: PAT entry 4 = write-combining, for the framebuffer window
    const pml4 = allocTable();

    // 1. All RAM in the physmap (physicalToVirtual(physical)) RW + NX. No identity/low-half
    //    mapping: the low half belongs to user space. MMIO is skipped here and
    //    mapped on demand (mapMmio) or explicitly below.
    for (regions(boot_information.memory_map)) |r| {
        if (r.kind == .mmio) continue;
        mapRangePhysmap(pml4, r.base, r.pages * page_size, present | writable | no_execute, false);
    }

    // 2. Physmap windows for the framebuffer and the Local APIC (device memory
    //    the kernel touches directly), RW + NX. The framebuffer is **write-
    //    combining** (see setupPat) so the console's full-screen clear is a burst,
    //    not millions of uncached single-word writes.
    const fb = boot_information.framebuffer;
    mapRangePhysmap(pml4, fb.base, @as(u64, fb.height) * fb.pitch, present | writable | no_execute, true);
    mapPage(pml4, boot_handoff.physicalToVirtual(0xFEE00000), 0xFEE00000, present | writable | no_execute);

    // 3. The kernel's own segments at their higher-half link addresses, mapped
    //    to their low physical load addresses with real ELF permissions: code
    //    R+X, rodata R, data R+W+NX. This is the W^X guarantee.
    for (boot_information.kernel_segments[0..boot_information.kernel_segment_count]) |seg| {
        var flags: u64 = present;
        if (seg.flags & pf_w != 0) flags |= writable;
        if (seg.flags & pf_x == 0) flags |= no_execute;
        var off: u64 = 0;
        while (off < seg.pages * page_size) : (off += page_size) {
            mapPage(pml4, seg.virtual + off, seg.physical + off, flags);
        }
    }

    // 4. Pre-create every higher-half PML4 entry (an empty PDPT where none
    //    exists yet), so the whole kernel half is a fixed set of top-level
    //    slots. A process address space (M3) then shares the kernel half simply
    //    by copying PML4[256..512) — growth beneath these slots (heap, on-demand
    //    MMIO) propagates to every address space because they share the PDPTs.
    for (256..512) |i| {
        const e = &tableAt(pml4)[i];
        if (e.* & present == 0) e.* = allocTable() | present | writable;
    }

    kernel_pml4 = pml4;
    asm volatile ("mov %[pml4], %%cr3"
        :
        : [pml4] "r" (pml4),
        : .{ .memory = true });
    on_own_tables = true; // now on the kernel's physmap (covers all RAM)
    init_done = true; // the kernel half is fixed from here
}

/// The kernel's own top-level page table (physical). Every kernel task and every
/// per-process address space shares this table's higher half.
pub fn kernelPml4() u64 {
    return kernel_pml4;
}

/// Load CR3 (switch the active address space). `pml4` is a physical frame.
pub fn loadCr3(pml4: u64) void {
    asm volatile ("mov %[pml4], %%cr3"
        :
        : [pml4] "r" (pml4),
        : .{ .memory = true });
}

/// Map a page into the kernel address space on demand (for the heap, etc.).
/// `writable_page` controls W; pages are always mapped non-executable.
pub fn map(virtual: u64, physical: u64, writable_page: bool) void {
    var flags: u64 = present | no_execute;
    if (writable_page) flags |= writable;
    mapPage(kernel_pml4, virtual, physical, flags);
    invalidate(virtual);
}

/// Map a device MMIO range into the physmap and return the virtual address to
/// use for it (physicalToVirtual(physical)). The single way the kernel (and the device
/// layer, via the HAL) reaches memory-mapped registers once the identity map is
/// gone: physmap pages are RW + NX, so a driver never executes device memory.
/// Idempotent for already-mapped ranges. `len` 0 maps one page.
pub fn mapMmio(physical: u64, len: u64, writable_page: bool) u64 {
    var flags: u64 = present | no_execute;
    if (writable_page) flags |= writable;
    const first = physical & ~@as(u64, page_size - 1);
    const last = physical + (if (len == 0) 1 else len) - 1;
    var address = first;
    while (address <= (last & ~@as(u64, page_size - 1))) : (address += page_size) {
        const virtual = boot_handoff.physicalToVirtual(address);
        mapPage(kernel_pml4, virtual, address, flags);
        invalidate(virtual);
    }
    return boot_handoff.physicalToVirtual(physical);
}

/// Like `descend`, but also sets the U/S bit on the intermediate entry (new or
/// pre-existing): ring-3 access requires U at *every* level, and `descend` leaves
/// existing entries untouched. Only used under user-exclusive virtual ranges, so
/// no kernel mapping's protection is widened (the leaf still governs).
fn descendUser(entry: *u64) u64 {
    const table = descend(entry);
    entry.* |= user;
    return table;
}

/// Map one 4 KiB page `virtual` -> `physical` accessible from ring 3. W^X is the
/// caller's contract: code pages are read-only + executable, data pages are
/// writable + no-execute. `virtual` must lie in a user-exclusive region (see
/// `descendUser`).
pub fn mapUser(virtual: u64, physical: u64, writable_page: bool, executable: bool) void {
    mapUserInto(kernel_pml4, virtual, physical, writable_page, executable);
}

/// Map a ring-3-accessible page into the address space rooted at `pml4` (which
/// may be a process's own table or the kernel's). W^X is the caller's contract.
pub fn mapUserInto(pml4: u64, virtual: u64, physical: u64, writable_page: bool, executable: bool) void {
    var flags: u64 = present | user;
    if (writable_page) flags |= writable;
    if (!executable) flags |= no_execute;
    const pml4e = &tableAt(pml4)[(virtual >> 39) & 0x1FF];
    const pdpt = descendUser(pml4e);
    const pdpte = &tableAt(pdpt)[(virtual >> 30) & 0x1FF];
    const pd = descendUser(pdpte);
    const pde = &tableAt(pd)[(virtual >> 21) & 0x1FF];
    const pt = descendUser(pde);
    tableAt(pt)[(virtual >> 12) & 0x1FF] = (physical & address_mask) | flags;
    invalidate(virtual);
}

/// Map a device MMIO window `[physical, physical+len)` into the user (low) half of the
/// address space rooted at `pml4`, page by page. Unlike `mapUserInto` these pages
/// are **strong-uncacheable** (PCD|PWT — device registers must not be cached) and
/// carry the `device_grant` bit so teardown does not return the MMIO frames to the
/// RAM allocator (`freeSubtree`). RW + NX; the caller places `virtual` in a
/// user-exclusive range (PML4[225]). Both `virtual` and `physical` are page-aligned by
/// the caller; a sub-page `physical` offset is the caller's to re-apply.
pub fn mapUserDeviceInto(pml4: u64, virtual: u64, physical: u64, len: u64, write_combining: bool) void {
    // Registers are strong-uncacheable (PCD|PWT). A framebuffer instead wants
    // write-combining — the PAT bit (bit 7 in a 4 KiB PTE) with PCD=PWT=0 selects PAT
    // entry 4, which `setupPat` programs to WC — so pixel writes batch into bursts.
    const cache: u64 = if (write_combining) pte_pat else (pcd | pwt);
    const flags: u64 = present | user | writable | no_execute | device_grant | cache;
    const first = physical & ~@as(u64, page_size - 1);
    const last = (physical + (if (len == 0) 1 else len) - 1) & ~@as(u64, page_size - 1);
    var off: u64 = 0;
    while (first + off <= last) : (off += page_size) {
        const v = virtual + off;
        const pml4e = &tableAt(pml4)[(v >> 39) & 0x1FF];
        const pdpt = descendUser(pml4e);
        const pdpte = &tableAt(pdpt)[(v >> 30) & 0x1FF];
        const pd = descendUser(pdpte);
        const pde = &tableAt(pd)[(v >> 21) & 0x1FF];
        const pt = descendUser(pde);
        tableAt(pt)[(v >> 12) & 0x1FF] = ((first + off) & address_mask) | flags;
        invalidate(v);
    }
}

/// Map `[physical, physical+len)` into the user half rooted at `pml4` as **coherent
/// DMA memory**: strong-uncacheable (PCD|PWT — a device reads/writes this RAM without
/// snooping the CPU caches) but, unlike `mapUserDeviceInto`, **without** `device_grant`
/// — because these frames are real RAM from `pmm.allocContiguous`, so teardown
/// (`freeSubtree`) must return them to the allocator like any other user page. RW + NX.
/// The caller aligns `virtual`/`physical` and places `virtual` in the DMA arena.
pub fn mapUserDmaInto(pml4: u64, virtual: u64, physical: u64, len: u64) void {
    const flags: u64 = present | user | writable | no_execute | pcd | pwt;
    const first = physical & ~@as(u64, page_size - 1);
    const last = (physical + (if (len == 0) 1 else len) - 1) & ~@as(u64, page_size - 1);
    var off: u64 = 0;
    while (first + off <= last) : (off += page_size) {
        const v = virtual + off;
        const pml4e = &tableAt(pml4)[(v >> 39) & 0x1FF];
        const pdpt = descendUser(pml4e);
        const pdpte = &tableAt(pdpt)[(v >> 30) & 0x1FF];
        const pd = descendUser(pdpte);
        const pde = &tableAt(pd)[(v >> 21) & 0x1FF];
        const pt = descendUser(pde);
        tableAt(pt)[(v >> 12) & 0x1FF] = ((first + off) & address_mask) | flags;
        invalidate(v);
    }
}

/// The raw leaf entry mapping `virtual` in the address space rooted at `pml4`, or null
/// if any level of the walk is absent. **Read-only** — never allocates or descends into
/// a missing table (unlike the `map*` paths' `descendUser`). Stops at the first huge
/// leaf. For tests and introspection that need a page's actual flag bits.
pub fn leafEntryOf(pml4: u64, virtual: u64) ?u64 {
    const l4 = tableAt(pml4)[(virtual >> 39) & 0x1FF];
    if (l4 & present == 0) return null;
    const l3 = tableAt(l4 & address_mask)[(virtual >> 30) & 0x1FF];
    if (l3 & present == 0) return null;
    if (l3 & page_size_bit != 0) return l3; // 1 GiB leaf
    const l2 = tableAt(l3 & address_mask)[(virtual >> 21) & 0x1FF];
    if (l2 & present == 0) return null;
    if (l2 & page_size_bit != 0) return l2; // 2 MiB leaf
    const l1 = tableAt(l2 & address_mask)[(virtual >> 12) & 0x1FF];
    if (l1 & present == 0) return null;
    return l1;
}

/// Is the 4 KiB leaf mapping `virtual` write-combining — the PAT bit set with PCD and
/// PWT clear, which `setupPat` makes PAT entry 4 (WC)? Null if unmapped. The device
/// mapping path (`mapUserDeviceInto`) always uses 4 KiB leaves, so bit 7 (`pte_pat`)
/// is the PAT selector in play.
pub fn leafIsWriteCombining(pml4: u64, virtual: u64) ?bool {
    const e = leafEntryOf(pml4, virtual) orelse return null;
    return (e & pte_pat != 0) and (e & pcd == 0) and (e & pwt == 0);
}

/// Map `[physical, physical+len)` into the user half rooted at `pml4` as **shared cacheable
/// RAM**: write-back cacheable (RW + NX) for CPU compositing, and carrying `device_grant`
/// so teardown (`freeSubtree`) does **not** return the frames to the allocator. The frames
/// are owned by a refcounted shared-memory object (system/kernel/ipc-synchronous.zig) and
/// freed only when its last capability drops — not when one sharer's address space dies, or
/// the other sharers would be left mapping freed RAM. The caller aligns `virtual`/`physical`.
pub fn mapUserSharedInto(pml4: u64, virtual: u64, physical: u64, len: u64) void {
    const flags: u64 = present | user | writable | no_execute | device_grant; // WB cacheable
    const first = physical & ~@as(u64, page_size - 1);
    const last = (physical + (if (len == 0) 1 else len) - 1) & ~@as(u64, page_size - 1);
    var off: u64 = 0;
    while (first + off <= last) : (off += page_size) {
        const v = virtual + off;
        const pml4e = &tableAt(pml4)[(v >> 39) & 0x1FF];
        const pdpt = descendUser(pml4e);
        const pdpte = &tableAt(pdpt)[(v >> 30) & 0x1FF];
        const pd = descendUser(pdpte);
        const pde = &tableAt(pd)[(v >> 21) & 0x1FF];
        const pt = descendUser(pde);
        tableAt(pt)[(v >> 12) & 0x1FF] = ((first + off) & address_mask) | flags;
        invalidate(v);
    }
}

/// Create a new address space: a fresh PML4 with an empty user half and the
/// kernel's higher half shared in (copying PML4[256..512), whose entries point
/// at the kernel's PDPTs — pre-created at init and never restaled, so growth in
/// the kernel half propagates to every address space). Returns the physical
/// PML4, or null if out of frames.
pub fn createAddressSpace() ?u64 {
    const pml4 = alloc_frame() orelse return null;
    const t = tableAt(pml4);
    @memset(t[0..256], 0); // empty user half
    @memcpy(t[256..512], tableAt(kernel_pml4)[256..512]); // shared kernel half
    return pml4;
}

/// Tear down an address space created by `createAddressSpace`: free every frame
/// and table in the user half [0..256), then the PML4 itself. The shared kernel
/// half [256..512) is never touched. The caller must not be running on `pml4`.
pub fn destroyAddressSpace(pml4: u64) void {
    const t = tableAt(pml4);
    for (0..256) |i| {
        if (t[i] & present != 0) freeSubtree(t[i] & address_mask, 3); // PDPT level
    }
    free_frame(pml4);
}

/// Recursively free a page-table subtree: `level` 3 = PDPT, 2 = PD, 1 = PT. At
/// level 1 the entries are leaf data frames; above, they are child tables.
fn freeSubtree(physical: u64, level: u32) void {
    const t = tableAt(physical);
    for (t) |e| {
        if (e & present == 0) continue;
        if (level > 1) {
            freeSubtree(e & address_mask, level - 1);
        } else if (e & device_grant == 0) {
            // A device-grant leaf points at MMIO, not RAM — returning it to the
            // frame allocator would corrupt the pool. Only reclaim real RAM.
            free_frame(e & address_mask);
        }
    }
    free_frame(physical); // page-table frames are always real RAM
}

/// Whether `virtual` is currently mapped **executable** — present with the NX bit
/// clear. Walks the 4-level tables, stopping at a 2 MiB huge-page leaf (the physmap
/// uses them). Returns false if unmapped. Used for W^X checks in tests.
pub fn isExecutable(virtual: u64) bool {
    const pml4e = tableAt(kernel_pml4)[(virtual >> 39) & 0x1FF];
    if (pml4e & present == 0) return false;
    const pdpte = tableAt(pml4e & address_mask)[(virtual >> 30) & 0x1FF];
    if (pdpte & present == 0) return false;
    const pde = tableAt(pdpte & address_mask)[(virtual >> 21) & 0x1FF];
    if (pde & present == 0) return false;
    if (pde & page_size_bit != 0) return pde & no_execute == 0; // 2 MiB huge leaf
    const pte = tableAt(pde & address_mask)[(virtual >> 12) & 0x1FF];
    if (pte & present == 0) return false;
    return pte & no_execute == 0;
}

/// Make an already-identity-mapped RAM page **executable** (clear its NX bit),
/// leaving it present and writable. The blanket RAM mapping is NX for W^X, but the
/// application processors fetch the AP trampoline from a low RAM page under paging —
/// so that one page must be executable. A deliberate, temporary W^X exception for a
/// single bring-up page; the caller frees it once every AP is up.
pub fn setExecutable(physical: u64) void {
    mapPage(kernel_pml4, physical, physical, present | writable); // note: no no_execute
    invalidate(physical);
}

/// Remove a mapping and flush it from the TLB.
pub fn unmap(virtual: u64) void {
    unmapInto(kernel_pml4, virtual);
}

/// Remove a mapping from the address space rooted at `pml4` (a process's own
/// table or the kernel's) and flush it from the TLB. Clears only the leaf PTE —
/// the intermediate tables and any frame the PTE pointed at are left to the
/// caller (munmap frees the frame; `destroyAddressSpace` reclaims the tables).
pub fn unmapInto(pml4: u64, virtual: u64) void {
    const pml4e = tableAt(pml4)[(virtual >> 39) & 0x1FF];
    if (pml4e & present == 0) return;
    const pdpte = tableAt(pml4e & address_mask)[(virtual >> 30) & 0x1FF];
    if (pdpte & present == 0) return;
    const pde = tableAt(pdpte & address_mask)[(virtual >> 21) & 0x1FF];
    if (pde & present == 0) return;
    tableAt(pde & address_mask)[(virtual >> 12) & 0x1FF] = 0;
    invalidate(virtual);
}

/// Resolve a virtual address to a physical one in the address space rooted at
/// `pml4`, walking the tables through the physmap (CR3-independent — works for
/// any address space, not just the live one). Returns null if `virtual` is not
/// mapped at any level. Stops at a 2 MiB huge-page leaf (the physmap uses them),
/// resolving the offset within it. The foundation for cross-address-space copies
/// and for munmap (which needs the frame behind a user virtual_address to free it).
pub fn translateIn(pml4: u64, virtual: u64) ?u64 {
    const pml4e = tableAt(pml4)[(virtual >> 39) & 0x1FF];
    if (pml4e & present == 0) return null;
    const pdpte = tableAt(pml4e & address_mask)[(virtual >> 30) & 0x1FF];
    if (pdpte & present == 0) return null;
    const pde = tableAt(pdpte & address_mask)[(virtual >> 21) & 0x1FF];
    if (pde & present == 0) return null;
    if (pde & page_size_bit != 0) // 2 MiB huge leaf: frame base is bits 51:21
        return (pde & address_mask & ~@as(u64, huge_page_size - 1)) | (virtual & (huge_page_size - 1));
    const pte = tableAt(pde & address_mask)[(virtual >> 12) & 0x1FF];
    if (pte & present == 0) return null;
    return (pte & address_mask) | (virtual & (page_size - 1));
}

fn invalidate(virtual: u64) void {
    // invlpg needs its operand via a register-indirect memory reference that Zig
    // inline asm won't form directly, so stage the address in a register first.
    asm volatile (
        \\mov %[v], %%rax
        \\invlpg (%%rax)
        :
        : [v] "r" (virtual),
        : .{ .rax = true, .memory = true });
}
