mem: implicit-context foundation + many compiler fixes
The session-long set of changes that lay the groundwork for the
Jai-literal implicit-Context-parameter refactor. Lots of accumulated
work; the new arrival is the implicit-ctx foundation (steps 1+2 of
the plan in current/CHECKPOINT-MEM.md):
Step 1 — `CAllocator :: struct {}` stateless allocator in
library/modules/allocators.sx, delegating directly to
libc_malloc/libc_free. `ConstantValue` in src/ir/inst.zig gains a
`func_ref: FuncId` leaf so nested aggregates can carry function
pointers (the inline Allocator value's fn-ptr fields). Switch
sites updated in emit_llvm.zig, print.zig, interp.zig.
Step 2 — `emitDefaultContextGlobal` in src/ir/lower.zig synthesises
a static `__sx_default_context` global with a nested-aggregate
init_val pointing at the CAllocator → Allocator thunks. The
second-pass `initVtableGlobals` in emit_llvm.zig is generalised
to handle `.aggregate` init_vals (re-emits after func_map is
populated so func_ref leaves resolve to real symbols).
Also folded in from earlier work this session:
- Phase 1.1: `xx value` heap-copy in `buildProtocolValue` routes
through `context.allocator` via the new `allocViaContext` helper.
- interp.zig: `marshalForeignArg` double-offset bug fixed —
`heapSlice` already adds `hp.offset` to the slice ptr, so the
extra `+ hp.offset` was scribbling memcpy/memset into adjacent
heap state, corrupting `heap.items[0]`. Symptom: `build_format`
at comptime produced zero bytes, all `print` calls failed.
- Lazy lowering: `lazyLowerFunction` now declares foreign-body
functions as extern stubs in the local (comptime) module so
cross-module foreign calls resolve.
- Allocator API: all stdlib allocators on one-line `init() -> *T`
(CAllocator/GPA: libc-backed; Arena/TrackingAllocator: parent-
backed; BufAlloc: embeds state at head of user buffer).
- issues 0038 (transitive #import), 0039 (chess + stdlib migration
fallout), 0040 (generic struct method dot-dispatch), 0041
(pointer types as type-arg), 0042 (alias name resolution) — all
fixed; regression tests in examples/.
- Diagnostic: `emitError` now embeds the lowering's
`current_source_file` and enclosing function in the literal
message; SX_TRACE_UNRESOLVED=1 dumps a Zig stack trace at the
emit site so misattributed spans can't hide where the failure
is.
- tools/verify-step.sh (all-platforms gate) and tools/scratch.sh
(interp/codegen parity tester) added.
Test suite: 152 example tests pass; chess builds + screenshots on
macOS / iOS sim / Android.
This commit is contained in:
@@ -681,32 +681,40 @@ pub const LLVMEmitter = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize vtable globals with function pointer constants.
|
||||
/// Must run after Pass 1 (function declarations) so func_map is populated.
|
||||
/// Initialize vtable + aggregate-with-func_ref globals with function
|
||||
/// pointer constants. Must run after Pass 1 (function declarations) so
|
||||
/// func_map is populated — that's why these globals get a placeholder
|
||||
/// initializer in `emitGlobals` and we fix them up here.
|
||||
fn initVtableGlobals(self: *LLVMEmitter) void {
|
||||
for (self.ir_mod.globals.items, 0..) |global, i| {
|
||||
const iv = global.init_val orelse continue;
|
||||
const func_ids = switch (iv) {
|
||||
.vtable => |ids| ids,
|
||||
else => continue,
|
||||
};
|
||||
|
||||
const llvm_global = self.global_map.get(@intCast(i)) orelse continue;
|
||||
const llvm_ty = self.toLLVMType(global.ty);
|
||||
|
||||
// Build constant struct of function pointers
|
||||
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
||||
defer field_vals.deinit(self.alloc);
|
||||
for (func_ids) |fid| {
|
||||
const llvm_func = self.func_map.get(fid.index()) orelse {
|
||||
field_vals.append(self.alloc, c.LLVMConstNull(self.cached_ptr)) catch unreachable;
|
||||
continue;
|
||||
};
|
||||
field_vals.append(self.alloc, llvm_func) catch unreachable;
|
||||
switch (iv) {
|
||||
.vtable => |func_ids| {
|
||||
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
||||
defer field_vals.deinit(self.alloc);
|
||||
for (func_ids) |fid| {
|
||||
const llvm_func = self.func_map.get(fid.index()) orelse {
|
||||
field_vals.append(self.alloc, c.LLVMConstNull(self.cached_ptr)) catch unreachable;
|
||||
continue;
|
||||
};
|
||||
field_vals.append(self.alloc, llvm_func) catch unreachable;
|
||||
}
|
||||
const init_val = c.LLVMConstNamedStruct(llvm_ty, field_vals.items.ptr, @intCast(field_vals.items.len));
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
c.LLVMSetGlobalConstant(llvm_global, 1);
|
||||
},
|
||||
.aggregate => |agg| {
|
||||
// Re-emit. The first pass in `emitGlobals` already ran,
|
||||
// but func_ref leaves resolved to null then (func_map
|
||||
// wasn't populated yet). Now they resolve properly.
|
||||
const init_val = self.emitConstAggregate(agg, llvm_ty);
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
},
|
||||
else => continue,
|
||||
}
|
||||
const init_val = c.LLVMConstNamedStruct(llvm_ty, field_vals.items.ptr, @intCast(field_vals.items.len));
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
c.LLVMSetGlobalConstant(llvm_global, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2241,39 +2249,6 @@ pub const LLVMEmitter = struct {
|
||||
.call_builtin => |bi| {
|
||||
// Builtins that map to libc functions or LLVM intrinsics
|
||||
switch (bi.builtin) {
|
||||
.malloc => {
|
||||
const size = self.coerceArg(self.resolveRef(bi.args[0]), self.sizeType());
|
||||
const malloc_fn = self.getOrDeclareMalloc();
|
||||
var args = [_]c.LLVMValueRef{size};
|
||||
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMallocType(), malloc_fn, &args, 1, "malloc"));
|
||||
},
|
||||
.free => {
|
||||
const ptr = self.resolveRef(bi.args[0]);
|
||||
const free_fn = self.getOrDeclareFree();
|
||||
var args = [_]c.LLVMValueRef{ptr};
|
||||
_ = c.LLVMBuildCall2(self.builder, self.getFreeType(), free_fn, &args, 1, "");
|
||||
self.advanceRefCounter();
|
||||
},
|
||||
.memcpy => {
|
||||
const dst = self.resolveRef(bi.args[0]);
|
||||
const src = self.resolveRef(bi.args[1]);
|
||||
const len = self.coerceArg(self.resolveRef(bi.args[2]), self.sizeType());
|
||||
const memcpy_fn = self.getOrDeclareMemcpy();
|
||||
var args = [_]c.LLVMValueRef{ dst, src, len };
|
||||
_ = c.LLVMBuildCall2(self.builder, self.getMemcpyType(), memcpy_fn, &args, 3, "");
|
||||
self.advanceRefCounter();
|
||||
},
|
||||
.memset => {
|
||||
const dst = self.resolveRef(bi.args[0]);
|
||||
var val = self.resolveRef(bi.args[1]);
|
||||
const len = self.coerceArg(self.resolveRef(bi.args[2]), self.sizeType());
|
||||
// memset expects i32 for byte value — coerce width
|
||||
val = self.coerceArg(val, self.cached_i32);
|
||||
const memset_fn = self.getOrDeclareMemset();
|
||||
var args = [_]c.LLVMValueRef{ dst, val, len };
|
||||
_ = c.LLVMBuildCall2(self.builder, self.getMemsetType(), memset_fn, &args, 3, "");
|
||||
self.advanceRefCounter();
|
||||
},
|
||||
.sqrt, .sin, .cos, .floor => {
|
||||
const val = self.resolveRef(bi.args[0]);
|
||||
const val_ty = c.LLVMTypeOf(val);
|
||||
@@ -3699,6 +3674,7 @@ pub const LLVMEmitter = struct {
|
||||
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(elem_ty),
|
||||
else => c.LLVMConstNull(elem_ty),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user