kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 9ce8b07f265cea0852745786b5e21c200a3db146
parent 6e1392b32a02086c654ad381990a08cd3205f96f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat,  9 May 2026 11:01:21 -0700

lib: route exec memory and current-time through host CfreeEnv vtables

libcfree was pulling mmap/mprotect/munmap/__clear_cache/sysconf into the
JIT mapper, the emu code region, and ELF segment layout, plus
getenv/time/gmtime into the preprocessor for __DATE__/__TIME__. None of
those belong in a freestanding core.

Adds two new fields to CfreeEnv:

  - execmem (CfreeExecMem* vtable: page_size, reserve, protect, release,
    flush_icache; with CFREE_PROT_* flags). Required by JIT and emu;
    consulted by link_layout for segment alignment with a 16 KiB
    fallback when NULL.
  - now (int64_t unix seconds, or negative for "no clock"). The
    preprocessor breaks it into UTC y/M/d/h/m/s inline via
    civil_from_days, dropping the gmtime call entirely. Negative falls
    back to the C11 §6.10.8.1 placeholders.

Driver wires both: a posix-backed CfreeExecMem (mmap/mprotect/munmap +
__builtin___clear_cache) and SOURCE_DATE_EPOCH-then-time(NULL) for
`now`. The two JIT-using test harnesses (jit_runner, cg_runner) get
inline posix shims of their own. The lib_deps.allowlist drops _strtoll
since the SOURCE_DATE_EPOCH parsing moved to the driver.

After this change, libcfree.a's external symbol set is exactly the
allowlist (memcpy/memset/memcmp/strlen/setjmp/longjmp + __*_chk and
__stack_chk_*); the test-lib-deps target passes.

test/test.mk: switch test-lib-deps to a temp-file diff so it runs
under /bin/sh (process substitution was bash-only).

Diffstat:
Mdriver/driver.h | 38+++++++++++++++++++++++++++++++++++---
Mdriver/env.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/cfree.h | 39+++++++++++++++++++++++++++++++++++++++
Msrc/emu/runtime.c | 17+++++++++--------
Msrc/link/link_jit.c | 93++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/link/link_layout.c | 21++++++++++++++-------
Msrc/pp/pp.c | 71++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mtest/cg/harness/cg_runner.c | 38++++++++++++++++++++++++++++++++++++++
Mtest/lib_deps.allowlist | 1-
Mtest/link/harness/jit_runner.c | 41+++++++++++++++++++++++++++++++++++++++--
Mtest/test.mk | 7+++++--
11 files changed, 362 insertions(+), 81 deletions(-)

diff --git a/driver/driver.h b/driver/driver.h @@ -34,14 +34,46 @@ int driver_dbg (int argc, char** argv); int driver_run (int argc, char** argv); int driver_emu (int argc, char** argv); +/* Per-tool help printers. Write a multi-section help text to stdout and + * return. The tool entry-points call these when invoked with no args, -h, + * or --help (objdump excepts -h, since GNU objdump uses it for section + * headers — only --help triggers help there). */ +void driver_help_cc (void); +void driver_help_as (void); +void driver_help_ld (void); +void driver_help_ar (void); +void driver_help_objdump(void); +void driver_help_dbg (void); +void driver_help_run (void); +void driver_help_emu (void); + +/* Multi-call top-level help (`cfree`, `cfree -h`, `cfree --help`, + * `cfree help`). Lists each tool with a one-line summary and explains + * the multi-call dispatch. Writes to stdout. */ +void driver_help_top (void); + +/* Returns 1 if `arg` is "--help" or "-help". The short "-h" is treated + * as a help request by every tool except objdump (where it means + * "section headers"); callers that want to honour it should test it + * explicitly via driver_argv_wants_help(..., 1). */ +int driver_is_help_flag(const char* arg); + +/* Scan argv[1..argc-1] for a help request, returning 1 on a hit. Triggers + * on "--help" / "-help" always, and on "-h" when accept_short_h is set. + * Tool entries also treat argc < 2 (no positional or option args) as a + * help request — the convention is "no args means show help". */ +int driver_argv_wants_help(int argc, char** argv, int accept_short_h); + /* Shared host environment used by every tool that calls into libcfree. * driver_env_init wires up the libc-backed heap, the stderr diag sink, and * a POSIX file_io implementation (open/read/write on real paths). It is * the single piece of glue that turns "the host" into a CfreeEnv. */ typedef struct DriverEnv { - CfreeHeap* heap; - CfreeDiagSink* diag; - CfreeFileIO file_io; + CfreeHeap* heap; + CfreeDiagSink* diag; + CfreeFileIO file_io; + const CfreeExecMem* execmem; + int64_t now; /* unix seconds; -1 = unknown */ } DriverEnv; void driver_env_init(DriverEnv*); diff --git a/driver/env.c b/driver/env.c @@ -9,7 +9,9 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/mman.h> #include <sys/stat.h> +#include <time.h> #include <unistd.h> /* Host-side implementations of the three vtable interfaces libcfree needs: @@ -78,6 +80,56 @@ static CfreeDiagSink g_diag_stderr = { diag_stderr_emit, NULL, 0, 0, }; +/* ---------------- exec memory (mmap-backed) ---------------- */ + +static int cfree_to_posix_prot(int prot) +{ + int p = 0; + if (prot & CFREE_PROT_READ) p |= PROT_READ; + if (prot & CFREE_PROT_WRITE) p |= PROT_WRITE; + if (prot & CFREE_PROT_EXEC) p |= PROT_EXEC; + return p; +} + +static void* execmem_reserve(void* user, size_t size, int prot) +{ + void* p; + (void)user; + p = mmap(NULL, size, cfree_to_posix_prot(prot), + MAP_PRIVATE | MAP_ANON, -1, 0); + return (p == MAP_FAILED) ? NULL : p; +} + +static int execmem_protect(void* user, void* addr, size_t size, int prot) +{ + (void)user; + return mprotect(addr, size, cfree_to_posix_prot(prot)); +} + +static void execmem_release(void* user, void* addr, size_t size) +{ + (void)user; + munmap(addr, size); +} + +static void execmem_flush_icache(void* user, void* addr, size_t size) +{ + (void)user; +#if defined(__aarch64__) || defined(__arm__) + __builtin___clear_cache((char*)addr, (char*)addr + size); +#else + (void)addr; (void)size; +#endif +} + +static size_t host_page_size(void) +{ + long p = sysconf(_SC_PAGESIZE); + return (p > 0) ? (size_t)p : (size_t)0x4000; +} + +static CfreeExecMem g_execmem_posix; /* page_size set in driver_env_init */ + /* ---------------- writer (fd-backed) ---------------- */ typedef struct DriverFdWriter { @@ -220,6 +272,29 @@ void driver_env_init(DriverEnv* e) e->file_io.release = posix_release; e->file_io.open_writer = posix_open_writer; e->file_io.user = e; + + g_execmem_posix.page_size = host_page_size(); + g_execmem_posix.reserve = execmem_reserve; + g_execmem_posix.protect = execmem_protect; + g_execmem_posix.release = execmem_release; + g_execmem_posix.flush_icache = execmem_flush_icache; + g_execmem_posix.user = NULL; + e->execmem = &g_execmem_posix; + + /* Reproducible-build precedent: SOURCE_DATE_EPOCH wins over wall clock. + * If neither is set or the env value doesn't parse, advertise -1 ("no + * clock") and pp falls back to C11 placeholders. */ + { + const char* sde = getenv("SOURCE_DATE_EPOCH"); + if (sde && *sde) { + char* endp = NULL; + long long v = strtoll(sde, &endp, 10); + e->now = (endp != sde && v >= 0) ? (int64_t)v : (int64_t)-1; + } else { + time_t t = time(NULL); + e->now = (t == (time_t)-1) ? (int64_t)-1 : (int64_t)t; + } + } } void driver_env_fini(DriverEnv* e) @@ -234,6 +309,8 @@ CfreeEnv driver_env_to_cfree(const DriverEnv* e) ce.heap = e->heap; ce.file_io = &e->file_io; ce.diag = e->diag; + ce.execmem = e->execmem; + ce.now = e->now; return ce; } diff --git a/include/cfree.h b/include/cfree.h @@ -218,10 +218,49 @@ typedef struct CfreeFileIO { void* user; } CfreeFileIO; +/* Executable-memory vtable. Required by the JIT mapper (cfree_jit_from_image) + * and the emu runtime; consulted by the linker for page-aligned segment + * layout. May be NULL for hosts that never JIT and never run the emu — link + * layout falls back to a 16 KiB page in that case. Semantics mirror + * mmap/mprotect/munmap closely so a posix host needs only a thin shim. + * + * reserve — allocate `size` bytes (page_size-aligned) with initial + * perms `prot`. Returns NULL on failure. + * protect — change perms of [addr, addr+size) (page_size-aligned) + * to `prot`. Returns 0 on success, nonzero on failure. + * release — free a prior reservation; addr/size match the reserve + * call. + * flush_icache — make freshly written instructions in [addr, addr+size) + * visible to the CPU. May be a no-op on x86; required on + * aarch64 before transferring control to JITed code. + * + * `prot` is a bitmask of CFREE_PROT_*. */ +enum { + CFREE_PROT_NONE = 0, + CFREE_PROT_READ = 1 << 0, + CFREE_PROT_WRITE = 1 << 1, + CFREE_PROT_EXEC = 1 << 2, +}; + +typedef struct CfreeExecMem { + size_t page_size; + void* (*reserve) (void* user, size_t size, int prot); + int (*protect) (void* user, void* addr, size_t size, int prot); + void (*release) (void* user, void* addr, size_t size); + void (*flush_icache)(void* user, void* addr, size_t size); + void* user; +} CfreeExecMem; + typedef struct CfreeEnv { CfreeHeap* heap; const CfreeFileIO* file_io; /* may be NULL for purely in-memory pipelines */ CfreeDiagSink* diag; + const CfreeExecMem* execmem; /* NULL ok unless JIT/emu paths run */ + /* Unix seconds since 1970-01-01 UTC, or negative for "no clock". Used + * by the preprocessor for __DATE__ / __TIME__ (negative → C11 §6.10.8.1 + * placeholders). The host decides the policy (SOURCE_DATE_EPOCH, + * wall clock, fixed value); libcfree never reads either. */ + int64_t now; } CfreeEnv; /* ============================================================ diff --git a/src/emu/runtime.c b/src/emu/runtime.c @@ -105,11 +105,13 @@ size_t emu_code_region_size(const EmuCodeRegion* r) void emu_code_region_commit_rx_to(EmuCodeRegion* r, uintptr_t end) { + const CfreeExecMem* mem; uintptr_t base, page_end; size_t len; if (!r) return; + mem = require_execmem(r->c); base = (uintptr_t)r->base; - page_end = (uintptr_t)align_up_u64((u64)end, page_size_bytes()); + page_end = (uintptr_t)align_up_u64((u64)end, page_size_bytes(mem)); /* Monotonic: never lower the high-water; chaining patches * already-committed code and depends on it staying RX. */ if (page_end <= r->rx_end) return; @@ -121,13 +123,12 @@ void emu_code_region_commit_rx_to(EmuCodeRegion* r, uintptr_t end) * the original PROT_NONE mapping (which is technically a fault * unless the mapping was promoted to RW). The actual write path * is owned by link_resolve_extend; in v1 we expect the linker - * to use mprotect-RW prior to writing. RX flip happens here. */ - if (mprotect((void*)r->rx_end, len, PROT_READ | PROT_EXEC) == 0) { -#ifdef __aarch64__ - /* Flush data caches and invalidate icache so the CPU sees - * the freshly written instructions. */ - __builtin___clear_cache((char*)r->rx_end, (char*)page_end); -#endif + * to flip to RW prior to writing. RX flip happens here. */ + if (mem->protect(mem->user, (void*)r->rx_end, len, + CFREE_PROT_READ | CFREE_PROT_EXEC) == 0) { + if (mem->flush_icache) { + mem->flush_icache(mem->user, (void*)r->rx_end, len); + } r->rx_end = page_end; } } diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -1,7 +1,7 @@ -/* JIT mapper. Takes a resolved LinkImage, mmaps a fresh writable - * region, copies segments, applies relocations against the runtime - * base, mprotects to final permissions, and returns an owning CfreeJit - * handle. +/* JIT mapper. Takes a resolved LinkImage, reserves a fresh writable + * region (via env->execmem), copies segments, applies relocations + * against the runtime base, flips to final permissions, and returns + * an owning CfreeJit handle. * * Lookup is by interned Sym name (object-local handles never escape). * Inspector entries (cfree_jit_view, _addr_to_sym, _sym_iter_*) are @@ -18,17 +18,24 @@ #include <string.h> -#include <sys/mman.h> -#include <unistd.h> - static SrcLoc no_loc(void) { SrcLoc l = {0,0,0}; return l; } static u64 align_up_u64(u64 v, u64 a) { return (v + (a - 1u)) & ~(a - 1u); } -static u64 jit_page_size(void) +static const CfreeExecMem* require_execmem(Compiler* c) { - long p = sysconf(_SC_PAGESIZE); - return (p > 0) ? (u64)p : 0x4000u; + const CfreeExecMem* m = c->env ? c->env->execmem : NULL; + if (!m || !m->reserve || !m->protect || !m->release) { + compiler_panic(c, no_loc(), + "cfree_jit: env->execmem is required for JIT"); + } + return m; +} + +static u64 jit_page_size(Compiler* c) +{ + const CfreeExecMem* m = require_execmem(c); + return m->page_size ? (u64)m->page_size : 0x4000u; } struct CfreeJit { @@ -40,44 +47,49 @@ struct CfreeJit { static int perms_for(u32 secflags) { - int p = PROT_READ; - if (secflags & SF_EXEC) p |= PROT_EXEC; - if (secflags & SF_WRITE) p |= PROT_WRITE; + int p = CFREE_PROT_READ; + if (secflags & SF_EXEC) p |= CFREE_PROT_EXEC; + if (secflags & SF_WRITE) p |= CFREE_PROT_WRITE; return p; } CfreeJit* cfree_jit_from_image(LinkImage* img) { - Compiler* c; - Heap* heap; - CfreeJit* jit; - void* base; - size_t map_size = 0; - u32 i; + Compiler* c; + Heap* heap; + const CfreeExecMem* mem; + CfreeJit* jit; + void* base; + size_t map_size = 0; + u64 page; + u32 i; if (!img) return NULL; c = img->c; heap = img->heap; + mem = require_execmem(c); + page = jit_page_size(c); - /* Total mmap size = top of last segment, page-aligned. */ + /* Total mapping size = top of last segment, page-aligned. */ if (img->nsegments == 0) { compiler_panic(c, no_loc(), "cfree_jit_from_image: image has no segments"); } for (i = 0; i < img->nsegments; ++i) { const LinkSegment* seg = &img->segments[i]; - u64 end = seg->vaddr + align_up_u64(seg->mem_size, jit_page_size()); + u64 end = seg->vaddr + align_up_u64(seg->mem_size, page); if (end > map_size) map_size = (size_t)end; } - map_size = (size_t)align_up_u64((u64)map_size, jit_page_size()); + map_size = (size_t)align_up_u64((u64)map_size, page); - base = mmap(NULL, map_size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, -1, 0); - if (base == MAP_FAILED) { + base = mem->reserve(mem->user, map_size, + CFREE_PROT_READ | CFREE_PROT_WRITE); + if (!base) { compiler_panic(c, no_loc(), - "cfree_jit_from_image: mmap failed"); + "cfree_jit_from_image: execmem.reserve failed"); } - /* mmap returns zeroed memory — BSS is naturally zero. */ + /* Reservation is required to be zeroed — matches mmap MAP_ANON + * semantics; BSS is naturally zero. */ /* Copy each segment's file bytes to (base + vaddr). */ for (i = 0; i < img->nsegments; ++i) { @@ -106,27 +118,26 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) link_reloc_apply(c, r->kind, P_bytes, S, r->addend, P); } -#ifdef __aarch64__ /* Flush the data caches we just wrote and invalidate the icache - * so the CPU sees the new instructions before the mprotect flip. */ - __builtin___clear_cache((char*)base, (char*)base + map_size); -#endif + * so the CPU sees the new instructions before the protect flip. + * Host decides whether this is a no-op (typical on x86). */ + if (mem->flush_icache) mem->flush_icache(mem->user, base, map_size); /* Flip permissions per segment. */ for (i = 0; i < img->nsegments; ++i) { const LinkSegment* seg = &img->segments[i]; - size_t mlen = (size_t)align_up_u64(seg->mem_size, jit_page_size()); - if (mprotect((u8*)base + seg->vaddr, mlen, - perms_for(seg->flags)) != 0) { - munmap(base, map_size); + size_t mlen = (size_t)align_up_u64(seg->mem_size, page); + if (mem->protect(mem->user, (u8*)base + seg->vaddr, mlen, + perms_for(seg->flags)) != 0) { + mem->release(mem->user, base, map_size); compiler_panic(c, no_loc(), - "cfree_jit_from_image: mprotect failed"); + "cfree_jit_from_image: execmem.protect failed"); } } jit = (CfreeJit*)heap->alloc(heap, sizeof(*jit), _Alignof(CfreeJit)); if (!jit) { - munmap(base, map_size); + mem->release(mem->user, base, map_size); compiler_panic(c, no_loc(), "cfree_jit_from_image: oom on jit handle"); } @@ -160,10 +171,14 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) void cfree_jit_free(CfreeJit* jit) { - Heap* heap; + Heap* heap; + const CfreeExecMem* mem; if (!jit) return; heap = (Heap*)jit->c->env->heap; - if (jit->base && jit->map_size) munmap(jit->base, jit->map_size); + mem = jit->c->env->execmem; + if (jit->base && jit->map_size && mem && mem->release) { + mem->release(mem->user, jit->base, jit->map_size); + } if (jit->image) { /* link_image_free unfederes (no-op now) and releases storage. */ link_image_free(jit->image); diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -14,17 +14,24 @@ #include "core/heap.h" #include "core/pool.h" +#include <cfree.h> + #include <string.h> -#include <unistd.h> LinkImage* link_image_alloc(Compiler*); /* defined in link.c */ static SrcLoc no_loc(void) { SrcLoc l = {0,0,0}; return l; } -static u64 layout_page_size(void) +/* Page size used for ELF segment alignment. We pull from env->execmem + * when present (matches the eventual JIT mapping granularity) and fall + * back to 16 KiB otherwise — large enough for any current Linux/aarch64 + * loader. A future cross-link with mismatched host/target page sizes + * will need a target-derived value here instead. */ +static u64 layout_page_size(Linker* l) { - long p = sysconf(_SC_PAGESIZE); - return (p > 0) ? (u64)p : 0x4000u; + const CfreeExecMem* m = (l && l->c && l->c->env) ? l->c->env->execmem : NULL; + if (m && m->page_size) return (u64)m->page_size; + return 0x4000u; } /* Three-bucket segment partitioning by permission. */ @@ -436,7 +443,7 @@ static void layout_sections(Linker* l, LinkImage* img) u32 perms; if (!seg_count[b]) continue; align = (u64)seg_align[b]; - if (align < layout_page_size()) align = layout_page_size(); + if (align < layout_page_size(l)) align = layout_page_size(l); cursor = align_up_u64(cursor, align); seg = &img->segments[img->nsegments]; @@ -526,7 +533,7 @@ static void layout_commons(Linker* l, LinkImage* img) u64 end = img->segments[i].vaddr + img->segments[i].mem_size; if (end > vaddr) vaddr = end; } - vaddr = align_up_u64(vaddr, layout_page_size()); + vaddr = align_up_u64(vaddr, layout_page_size(l)); segs = (LinkSegment*)img->heap->realloc(img->heap, img->segments, sizeof(*img->segments) * img->nsegments, sizeof(*img->segments) * nseg, _Alignof(LinkSegment)); @@ -549,7 +556,7 @@ static void layout_commons(Linker* l, LinkImage* img) rw_seg->file_offset = vaddr; rw_seg->file_size = 0; rw_seg->mem_size = 0; - rw_seg->align = (u32)layout_page_size(); + rw_seg->align = (u32)layout_page_size(l); img->segment_bytes[img->nsegments] = NULL; img->segment_bytes_cap[img->nsegments] = 0; img->nsegments++; diff --git a/src/pp/pp.c b/src/pp/pp.c @@ -18,7 +18,6 @@ #include <stdlib.h> #include <string.h> -#include <time.h> /* ============================================================ * Internal types @@ -2680,36 +2679,70 @@ static void pp_intern_keywords(Pp* pp) pp->sym__pragma = pool_intern_cstr(p, "_Pragma"); } -/* Compute __DATE__ and __TIME__ from SOURCE_DATE_EPOCH (or wall clock). - * Per C11 §6.10.8.1: __DATE__ is "Mmm dd yyyy" (dd is space-padded if - * < 10), __TIME__ is "hh:mm:ss". Both are surrounded by quotes. */ +/* Decompose unix seconds into UTC y/M/d/h/m/s. Algorithm: Howard Hinnant, + * "chrono-compatible Low-Level Date Algorithms" (civil_from_days). Valid + * for any int64 input; uses floor division so negative epoch values + * (pre-1970) work correctly. */ +typedef struct PpYMD { + int y; /* full year, e.g. 2026 */ + int M; /* 1..12 */ + int d; /* 1..31 */ + int h; /* 0..23 */ + int m; /* 0..59 */ + int s; /* 0..59 */ +} PpYMD; + +static void pp_break_time(int64_t t, PpYMD* out) +{ + int64_t days, secs; + int64_t z, era, doe, yoe, y, doy, mp, d, mo; + /* Floor-divide t by 86400. */ + days = t / 86400; + secs = t - days * 86400; + if (secs < 0) { secs += 86400; days -= 1; } + out->h = (int)(secs / 3600); + out->m = (int)((secs / 60) % 60); + out->s = (int)(secs % 60); + + z = days + 719468; /* shift to era starting 0000-03-01 */ + era = (z >= 0 ? z : z - 146096) / 146097; + doe = z - era * 146097; /* [0,146096] */ + yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; /* [0,399] */ + y = yoe + era * 400; + doy = doe - (365*yoe + yoe/4 - yoe/100); /* [0,365] */ + mp = (5*doy + 2) / 153; /* [0,11], Mar=0 */ + d = doy - (153*mp + 2)/5 + 1; /* [1,31] */ + mo = mp + (mp < 10 ? 3 : -9); /* [1,12] */ + y += (mo <= 2); + out->y = (int)y; + out->M = (int)mo; + out->d = (int)d; +} + +/* Compute __DATE__ and __TIME__ from env->now (unix seconds, host-supplied; + * negative means "no clock"). Per C11 §6.10.8.1: __DATE__ is "Mmm dd yyyy" + * (dd is space-padded if < 10), __TIME__ is "hh:mm:ss". Both quoted. */ static void compute_date_time(Pp* pp) { static const char* mons[] = { "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" }; - char date[24]; - char tm[16]; - time_t t; - struct tm* g; - const char* sde = getenv("SOURCE_DATE_EPOCH"); - if (sde && *sde) { - t = (time_t)strtoll(sde, NULL, 10); - } else { - t = time(NULL); - } - g = gmtime(&t); - if (!g) { + char date[24]; + char tm[16]; + int64_t t = pp->c->env->now; + PpYMD ymd; + if (t < 0) { pp->val_date_str = pool_intern_cstr(pp->c->global, "\"??? ?? ????\""); pp->val_time_str = pool_intern_cstr(pp->c->global, "\"??:??:??\""); return; } + pp_break_time(t, &ymd); { - int dd = g->tm_mday, yyyy = 1900 + g->tm_year; + int dd = ymd.d, yyyy = ymd.y; int p = 0; date[p++] = '"'; - memcpy(date + p, mons[g->tm_mon], 3); p += 3; + memcpy(date + p, mons[ymd.M - 1], 3); p += 3; date[p++] = ' '; date[p++] = (dd >= 10) ? (char)('0' + dd / 10) : ' '; date[p++] = (char)('0' + dd % 10); @@ -2722,7 +2755,7 @@ static void compute_date_time(Pp* pp) pp->val_date_str = pool_intern(pp->c->global, date, (size_t)p); } { - int hh = g->tm_hour, mm = g->tm_min, ss = g->tm_sec; + int hh = ymd.h, mm = ymd.m, ss = ymd.s; int p = 0; tm[p++] = '"'; tm[p++] = (char)('0' + (hh / 10) % 10); diff --git a/test/cg/harness/cg_runner.c b/test/cg/harness/cg_runner.c @@ -29,6 +29,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> @@ -53,6 +54,37 @@ static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, } static CfreeDiagSink g_diag = { diag_emit, NULL, 0, 0 }; +/* posix-backed CfreeExecMem for the JIT path. */ +static int xm_to_posix(int p) +{ + int q = 0; + if (p & CFREE_PROT_READ) q |= PROT_READ; + if (p & CFREE_PROT_WRITE) q |= PROT_WRITE; + if (p & CFREE_PROT_EXEC) q |= PROT_EXEC; + return q; +} +static void* xm_reserve(void* u, size_t n, int p) +{ + (void)u; + void* a = mmap(NULL, n, xm_to_posix(p), MAP_PRIVATE | MAP_ANON, -1, 0); + return a == MAP_FAILED ? NULL : a; +} +static int xm_protect(void* u, void* a, size_t n, int p) +{ (void)u; return mprotect(a, n, xm_to_posix(p)); } +static void xm_release(void* u, void* a, size_t n) { (void)u; munmap(a, n); } +static void xm_flush(void* u, void* a, size_t n) +{ + (void)u; +#if defined(__aarch64__) || defined(__arm__) + __builtin___clear_cache((char*)a, (char*)a + n); +#else + (void)a; (void)n; +#endif +} +static CfreeExecMem g_execmem = { + 16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL, +}; + /* ---- helpers ---- */ static const CgCase* find_case(const char* name) @@ -150,6 +182,7 @@ static int mode_emit(const char* name, const char* out_path) CfreeTarget target; target_aarch64_linux(&target); CfreeEnv env; memset(&env, 0, sizeof env); env.heap = &g_heap; env.diag = &g_diag; + env.execmem = &g_execmem; env.now = -1; CfreeCompiler* cc_ = cfree_compiler_new(target, &env); if (!cc_) { fprintf(stderr, "cg-runner: compiler_new failed\n"); return 2; } @@ -193,6 +226,7 @@ static int mode_jit(const char* name) CfreeTarget target; target_aarch64_linux(&target); CfreeEnv env; memset(&env, 0, sizeof env); env.heap = &g_heap; env.diag = &g_diag; + env.execmem = &g_execmem; env.now = -1; CfreeCompiler* cc_ = cfree_compiler_new(target, &env); if (!cc_) { fprintf(stderr, "cg-runner: compiler_new failed\n"); return 2; } @@ -251,6 +285,10 @@ static int usage(void) int main(int argc, char** argv) { + { + long ps = sysconf(_SC_PAGESIZE); + if (ps > 0) g_execmem.page_size = (size_t)ps; + } if (argc < 2) return usage(); if (!strcmp(argv[1], "--list")) return mode_list(); else if (!strcmp(argv[1], "--expected") && argc == 3) return mode_expected(argv[2]); diff --git a/test/lib_deps.allowlist b/test/lib_deps.allowlist @@ -8,4 +8,3 @@ _memcpy _memset _setjmp _strlen -_strtoll diff --git a/test/link/harness/jit_runner.c b/test/link/harness/jit_runner.c @@ -26,6 +26,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> @@ -44,6 +45,36 @@ static void diag_fn(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, } static CfreeDiagSink g_diag = { diag_fn, NULL, 0, 0 }; +static int xm_to_posix(int p) +{ + int q = 0; + if (p & CFREE_PROT_READ) q |= PROT_READ; + if (p & CFREE_PROT_WRITE) q |= PROT_WRITE; + if (p & CFREE_PROT_EXEC) q |= PROT_EXEC; + return q; +} +static void* xm_reserve(void* u, size_t n, int p) +{ + (void)u; + void* a = mmap(NULL, n, xm_to_posix(p), MAP_PRIVATE | MAP_ANON, -1, 0); + return a == MAP_FAILED ? NULL : a; +} +static int xm_protect(void* u, void* a, size_t n, int p) +{ (void)u; return mprotect(a, n, xm_to_posix(p)); } +static void xm_release(void* u, void* a, size_t n) { (void)u; munmap(a, n); } +static void xm_flush(void* u, void* a, size_t n) +{ + (void)u; +#if defined(__aarch64__) || defined(__arm__) + __builtin___clear_cache((char*)a, (char*)a + n); +#else + (void)a; (void)n; +#endif +} +static CfreeExecMem g_execmem = { + 16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL, +}; + static int slurp(const char* path, uint8_t** out, size_t* len) { int fd = open(path, O_RDONLY); @@ -74,6 +105,10 @@ static void* extern_resolver(void* user, const char* name) int main(int argc, char** argv) { + { + long ps = sysconf(_SC_PAGESIZE); + if (ps > 0) g_execmem.page_size = (size_t)ps; + } int gc_sections = 0; int use_resolver = 0; int next_archive = 0; @@ -122,8 +157,10 @@ int main(int argc, char** argv) CfreeEnv env; memset(&env, 0, sizeof(env)); - env.heap = &g_heap; - env.diag = &g_diag; + env.heap = &g_heap; + env.diag = &g_diag; + env.execmem = &g_execmem; + env.now = -1; CfreeCompiler* c = cfree_compiler_new(target, &env); if (!c) { fprintf(stderr, "jit-runner: compiler_new failed\n"); return 2; } diff --git a/test/test.mk b/test/test.mk @@ -82,7 +82,10 @@ test-cg: lib $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER) # Fail if libcfree.a depends on any external symbol not in the allowlist. # Drift in either direction (new dep, or stale entry) is a failure. +LIB_DEPS_ACTUAL = build/libcfree.deps.txt + test-lib-deps: lib - @diff -u test/lib_deps.allowlist \ - <(python3 scripts/lib_external_deps.py $(LIB_AR)) \ + @mkdir -p $(dir $(LIB_DEPS_ACTUAL)) + @python3 scripts/lib_external_deps.py $(LIB_AR) > $(LIB_DEPS_ACTUAL) + @diff -u test/lib_deps.allowlist $(LIB_DEPS_ACTUAL) \ || { echo "libcfree.a external symbol set drifted from test/lib_deps.allowlist"; exit 1; }