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:
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; }