Files
goldeneye-pc-port/port/include/port_addr.h
T
Dan Turner d4cc71af0a macos: native Apple Silicon (arm64) support — address model, boot chain, 21/21 levels
Adds native arm64 macOS support on top of #88's Intel macOS layer.

The one structural change arm64 needs is an address model. The port realises
the N64's 32-bit address space *as host addresses* — DRAM at 0x70000000, cart
at 0x10000000, plus ~1700 linker-absolute symbols — which is only sound while
host addresses fit in 32 bits. Native arm64 cannot map below 4 GiB
(__PAGEZERO; a shrunken pagezero is SIGKILLed at exec; PIE is mandatory;
MAP_32BIT fails). So the whole space is relocated to a fixed host base,
PORT_ADDR_BASE (16 TiB), with portN64ToHost / portHostToN64 / PORT_N64PTR as
the conversion chokepoints. PORT_ADDR_BASE is 0 on every other platform, so
all of this is an identity there.

That relocation then surfaces a class of latent 64-bit bugs that are invisible
at base 0 — any place the decomp round-trips a pointer through a 32-bit
integer. The src/ changes here are that class: pointer-width/layout only, no
behaviour change, N64 branch textually unchanged, each documented in
docs/dev/findings.md (D298/M2).

Also: Mach-O absolute-symbol generation at the same base, #pragma weak ->
symbol equates, __x86_64__ -> PLATFORM_64BIT, Darwin crash handler/ucontext,
shm_open DRAM, the AppKit main-thread guard, and the sidecar generation that
was the real boot blocker.

Verified: 21/21 solo levels boot, render and run crash-free.
2026-10-04 10:50:01 +01:00

106 lines
4.2 KiB
C

#ifndef PORT_ADDR_H
#define PORT_ADDR_H
/*
* The N64 address-space window (M1 of the macOS port).
*
* The port realises the N64's 32-bit address space as a range of *host*
* addresses, because the game stores addresses in 32-bit fields and 32-bit
* arithmetic and expects `(u32)ptr` to round-trip:
*
* 0x00000000..0x0fffffff segmented DL addresses (never host memory)
* 0x10000000..0x1fffffff cart image + sidecars (CART_BASE)
* 0x40000000..0x6fffffff image-relative encoding (see below)
* 0x70000000..0x707fffff DRAM V1 ("virtual", s32-safe; all RAM symbols)
* 0x80000000..0x807fffff DRAM V2 (KSEG0 mirror of V1)
* 0xa0000000..0xbfffffff game-thread stacks
*
* On Windows/Linux that range starts at host address 0, so `(u32)ptr ==
* offset` and the pointer IS the N64 address. Native arm64 macOS cannot map
* below 4 GiB (__PAGEZONE; see docs/dev/MACOS-ARM64-PLAN.md §1), so there the
* whole range is shifted up by PORT_ADDR_BASE. Every game-visible 32-bit value
* is then IDENTICAL to the Windows build; only the u32->host-pointer direction
* adds the base. PORT_ADDR_BASE is 0 everywhere but macOS, so those builds are
* unchanged.
*
* Image-relative encoding: pointers into the executable image cannot be
* truncated into the window (the image is not inside it, and on arm64 it is
* PIE with a per-run slide). They are encoded as 0x40000000 + (ptr - image
* base). On Windows the image base is 0x140000000, so this equals the raw
* `(u32)ptr` truncation the D131 fix relied on; on macOS it makes the round
* trip explicit. Ported game code must use portN64ToHost/portHostToN64 (or
* PORT_N64PTR) rather than a bare cast wherever a 32-bit field carries a
* pointer that is not a plain N64 address.
*/
#include <stdint.h>
#include "platform.h"
#if defined(PLATFORM_MACOS) && defined(PLATFORM_ARM)
/* 16 TiB: reserved cleanly on Apple Silicon, far above libmalloc's regions and
* the mmap hint area, and well below fast3d_ptr_ok()'s 128 TiB bound. The
* committed port/src/*.darwin.s files are generated with the SAME base
* (scripts/gen_romassets.py --darwin --base 0x100000000000); portAddrInit()
* checks the two agree at startup. */
#define PORT_ADDR_BASE 0x100000000000ULL
#else
#define PORT_ADDR_BASE 0x0ULL
#endif
/* Size of the N64 address space. */
#define PORT_ADDR_WINDOW (1ULL << 32)
/* Bound on the executable image's size, for classifying a host pointer as
* image-relative. The binary is a few MB; 256 MiB is comfortably generous. */
#define PORT_IMAGE_MAX (256u << 20)
/* Set by portAddrInit(): the executable's load address, and whether the image
* lies outside the window (so a truncated pointer needs the 0x40000000
* encoding). False on Linux, true on Windows and macOS. */
#ifdef __cplusplus
extern "C" {
#endif
extern uintptr_t g_portImageBase;
extern int g_portUseImageRel;
/* Reserve the window (macOS only) and record the image base. Must run before
* any of the port's fixed mappings and before large allocations. */
void portAddrInit(void);
#ifdef __cplusplus
}
#endif
/* u32 N64/physical address -> live host pointer. */
static inline void *portN64ToHost(uint32_t a)
{
if (g_portUseImageRel && a >= 0x40000000u && a < 0x70000000u) {
return (void *)(g_portImageBase + (uintptr_t)(a - 0x40000000u));
}
return (void *)((uintptr_t)PORT_ADDR_BASE + (uintptr_t)a);
}
/* Host pointer -> u32, the inverse of portN64ToHost for in-window and image
* pointers. Out-of-range pointers fall back to raw truncation (the pre-M1
* behaviour); they should not be stored in 32-bit fields. */
static inline uint32_t portHostToN64(const void *p)
{
uintptr_t v = (uintptr_t)p;
if (v - (uintptr_t)PORT_ADDR_BASE < (uintptr_t)PORT_ADDR_WINDOW) {
return (uint32_t)(v - (uintptr_t)PORT_ADDR_BASE);
}
if (g_portUseImageRel && v >= g_portImageBase
&& v - g_portImageBase < (uintptr_t)PORT_IMAGE_MAX) {
return (uint32_t)(0x40000000u + (v - g_portImageBase));
}
return (uint32_t)v;
}
/* Cast-site helper for game code (the D3x ABI/layout class): reads a 32-bit
* address out of a field/expression and yields a typed pointer. Identity on
* Windows/Linux (PORT_ADDR_BASE == 0). */
#define PORT_N64PTR(T, expr) ((T *)portN64ToHost((uint32_t)(expr)))
#endif /* PORT_ADDR_H */