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:
@@ -84,8 +84,4 @@ main :: () {
|
||||
print("{}\n", size_of(f32));
|
||||
print("{}\n", size_of(Sx(f32)));
|
||||
print("{}\n", size_of(Foo));
|
||||
print("{}\n", size_of(Complex));
|
||||
|
||||
size := size_of(Sx);
|
||||
print("{}\n", size);
|
||||
}
|
||||
|
||||
47
examples/125-static-method-inline-xx-protocol-arg.sx
Normal file
47
examples/125-static-method-inline-xx-protocol-arg.sx
Normal file
@@ -0,0 +1,47 @@
|
||||
// Inline `xx` cast as the first argument to a struct static method must
|
||||
// flow the leading param's type into the cast — otherwise an `xx ptr`
|
||||
// targeting a protocol param falls back to s64 and the call frame is
|
||||
// corrupted, SIGTRAPping when the body dispatches through the field.
|
||||
//
|
||||
// Three call shapes that must all succeed:
|
||||
// 1. Named-variable receiver: `a : Allocator = xx p; T.init(a, ...)`
|
||||
// 2. Free function with inline xx: `make_t(xx p, ...)`
|
||||
// 3. Static method with inline xx: `T.init(xx p, ...)` ← used to crash
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/allocators.sx";
|
||||
|
||||
Box :: struct {
|
||||
parent: Allocator;
|
||||
first_ptr: *void;
|
||||
|
||||
init :: (parent_alloc: Allocator, size: s64) -> *Box {
|
||||
self : *Box = xx malloc(size_of(Box));
|
||||
self.parent = parent_alloc;
|
||||
self.first_ptr = self.parent.alloc(size);
|
||||
self;
|
||||
}
|
||||
}
|
||||
|
||||
make_box :: (parent_alloc: Allocator, size: s64) -> *Box {
|
||||
self : *Box = xx malloc(size_of(Box));
|
||||
self.parent = parent_alloc;
|
||||
self.first_ptr = self.parent.alloc(size);
|
||||
self;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
g_gpa : GPA = .{ alloc_count = 0 };
|
||||
|
||||
a : Allocator = xx @g_gpa;
|
||||
b1 := Box.init(a, 64);
|
||||
print("Box.init (named-var): ok\n");
|
||||
|
||||
b2 := make_box(xx @g_gpa, 64);
|
||||
print("make_box (inline-xx): ok\n");
|
||||
|
||||
b3 := Box.init(xx @g_gpa, 64);
|
||||
print("Box.init (inline-xx): ok\n");
|
||||
|
||||
0;
|
||||
}
|
||||
26
examples/126-xx-recover-then-dispatch.sx
Normal file
26
examples/126-xx-recover-then-dispatch.sx
Normal file
@@ -0,0 +1,26 @@
|
||||
// `xx allocator` recovers the typed concrete pointer (ctx) from a
|
||||
// protocol value. The recovery is read-only and must not perturb
|
||||
// subsequent dispatch through the protocol value, regardless of
|
||||
// whether the recovery happens BEFORE or AFTER the first dispatch.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
gpa := GPA.init();
|
||||
a : Allocator = xx gpa;
|
||||
|
||||
// Recover BEFORE first dispatch.
|
||||
recovered : *GPA = xx a;
|
||||
print("recovered == gpa? {}\n", recovered == gpa);
|
||||
|
||||
p := a.alloc(64);
|
||||
print("alloc count after first alloc: {}\n", gpa.alloc_count);
|
||||
|
||||
// Recover AFTER dispatch — still works.
|
||||
recovered2 : *GPA = xx a;
|
||||
print("recovered2 == gpa? {}\n", recovered2 == gpa);
|
||||
|
||||
a.dealloc(p);
|
||||
print("alloc count after dealloc: {}\n", gpa.alloc_count);
|
||||
0;
|
||||
}
|
||||
18
examples/127-import-non-transitive.sx
Normal file
18
examples/127-import-non-transitive.sx
Normal file
@@ -0,0 +1,18 @@
|
||||
// `#import` is non-transitive: when A imports B and B imports C, A
|
||||
// must NOT see C's top-level names. This file imports `b.sx` (which
|
||||
// in turn imports `c.sx`) and then deliberately references C's names
|
||||
// directly — the compiler is expected to reject the references with
|
||||
// "not visible; #import the module that declares it" diagnostics.
|
||||
//
|
||||
// `b.sx` ↔ `c.sx` together still compile: `b_only_fn`'s body sees
|
||||
// `c_only_fn` / `c_only_const` because b.sx directly imports c.sx.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "127-import-non-transitive/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("b_only_fn: {}\n", b_only_fn());
|
||||
print("c_only_fn direct: {}\n", c_only_fn());
|
||||
print("c_only_const direct: {}\n", c_only_const);
|
||||
0;
|
||||
}
|
||||
5
examples/127-import-non-transitive/b.sx
Normal file
5
examples/127-import-non-transitive/b.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
#import "c.sx";
|
||||
|
||||
b_only_fn :: () -> s32 {
|
||||
c_only_fn() + c_only_const;
|
||||
}
|
||||
2
examples/127-import-non-transitive/c.sx
Normal file
2
examples/127-import-non-transitive/c.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
c_only_fn :: () -> s32 { 42; }
|
||||
c_only_const :: 7;
|
||||
29
examples/128-import-dir-scan-order.sx
Normal file
29
examples/128-import-dir-scan-order.sx
Normal file
@@ -0,0 +1,29 @@
|
||||
// Regression test for issue-0039 — directory-import scan order.
|
||||
//
|
||||
// When a directory is imported, the resolver iterates files
|
||||
// alphabetically. Inside the directory, `aaa_uses.sx` comes BEFORE
|
||||
// `types.sx` but its `make_my` returns `MyEnum` (defined in
|
||||
// `types.sx`). The combined directory module must put `aaa_uses.sx`'s
|
||||
// transitive imports (which include `MyEnum`) into the global scan
|
||||
// stream BEFORE `aaa_uses.sx`'s own decls so the tagged_union for
|
||||
// MyEnum is registered before `make_my`'s return type is resolved.
|
||||
//
|
||||
// Pre-fix, the dir-import implementation appended each file's
|
||||
// `own_decls` before its `decls`, which inverted that order and
|
||||
// caused `MyEnum` to be registered as a placeholder struct via the
|
||||
// `resolveTypeName` fallback. The later `enum_decl` scan then
|
||||
// short-circuited via `findByName` and never upgraded the placeholder
|
||||
// to the real tagged_union, surfacing as "cannot infer enum type
|
||||
// for '.b'" at the `return .b(42)` site.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "128-import-dir-scan-order";
|
||||
|
||||
main :: () -> s32 {
|
||||
e := make_my();
|
||||
if e == {
|
||||
case .a: { print("a\n"); }
|
||||
case .b: (v) { print("b={}\n", v); }
|
||||
}
|
||||
0;
|
||||
}
|
||||
11
examples/128-import-dir-scan-order/aaa_uses.sx
Normal file
11
examples/128-import-dir-scan-order/aaa_uses.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// Alphabetically FIRST file in the directory. Its own decl uses
|
||||
// `MyEnum`, defined in a sibling file (`types.sx`). The directory
|
||||
// import pass must register `MyEnum` BEFORE this file's `make_my`
|
||||
// is scanned, or `MyEnum` falls back to a placeholder struct and
|
||||
// `return .b(42)` fails with "cannot infer enum type".
|
||||
|
||||
#import "types.sx";
|
||||
|
||||
make_my :: () -> MyEnum {
|
||||
return .b(42);
|
||||
}
|
||||
4
examples/128-import-dir-scan-order/types.sx
Normal file
4
examples/128-import-dir-scan-order/types.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
MyEnum :: enum {
|
||||
a;
|
||||
b: s32;
|
||||
}
|
||||
28
examples/129-generic-method-dot-call.sx
Normal file
28
examples/129-generic-method-dot-call.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// Dot-call dispatch for generic struct methods.
|
||||
//
|
||||
// Covers three shapes:
|
||||
// 1. non-generic method: h.plain()
|
||||
// 2. generic method, explicit type arg: h.sized(s32)
|
||||
// 3. generic method, inferred from val: h.taking(99)
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Holder :: struct {
|
||||
n: s64;
|
||||
|
||||
plain :: (self: *Holder) -> s64 { self.n; }
|
||||
sized :: (self: *Holder, $T: Type) -> s64 { size_of(T); }
|
||||
taking :: (self: *Holder, $T: Type, v: T) -> T { v; }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
h : *Holder = xx malloc(size_of(Holder));
|
||||
h.n = 7;
|
||||
|
||||
print("plain: {}\n", h.plain());
|
||||
print("sized s32: {}\n", h.sized(s32));
|
||||
print("sized s64: {}\n", h.sized(s64));
|
||||
print("taking explicit: {}\n", h.taking(s32, 42));
|
||||
print("taking inferred: {}\n", h.taking(99));
|
||||
0;
|
||||
}
|
||||
39
examples/130-xx-value-routes-through-context-allocator.sx
Normal file
39
examples/130-xx-value-routes-through-context-allocator.sx
Normal file
@@ -0,0 +1,39 @@
|
||||
// Phase 1.1 — the compiler-internal heap-copy that backs `xx value`
|
||||
// protocol erasure must dispatch through `context.allocator`, not call
|
||||
// libc malloc directly. So when a `push Context.{ allocator = tracer }`
|
||||
// block is active, a `xx struct_value` inside it MUST be allocated by
|
||||
// the tracker.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Tracer :: struct {
|
||||
count: s64;
|
||||
|
||||
init :: () -> *Tracer {
|
||||
t : *Tracer = xx malloc(size_of(Tracer));
|
||||
t.count = 0;
|
||||
t;
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for Tracer {
|
||||
alloc :: (self: *Tracer, size: s64) -> *void {
|
||||
self.count += 1;
|
||||
return malloc(size);
|
||||
}
|
||||
dealloc :: (self: *Tracer, ptr: *void) {
|
||||
free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
ByValue :: struct { x: s64; y: s64; }
|
||||
|
||||
main :: () -> s32 {
|
||||
tracer := Tracer.init();
|
||||
push Context.{ allocator = xx tracer, data = null } {
|
||||
bv : ByValue = .{ x = 1, y = 2 };
|
||||
ignore : Allocator = xx bv;
|
||||
_ = ignore;
|
||||
}
|
||||
print("Tracer.count = {}\n", tracer.count);
|
||||
0;
|
||||
}
|
||||
@@ -43,15 +43,14 @@ main :: () -> s32 {
|
||||
|
||||
print("listening on http://localhost:{}\n", PORT);
|
||||
|
||||
arena : Arena = ---;
|
||||
arena_alloc := arena.create(context.allocator, 65536);
|
||||
arena := Arena.init(context.allocator, 65536);
|
||||
logger := Logger.{ prefix = "http", count = 0 };
|
||||
|
||||
while true {
|
||||
client := accept(fd, null, null);
|
||||
if client < 0 { continue; }
|
||||
|
||||
push Context.{ allocator = arena_alloc, data = xx @logger } {
|
||||
push Context.{ allocator = xx arena, data = xx @logger } {
|
||||
handle(client);
|
||||
}
|
||||
|
||||
|
||||
@@ -1141,6 +1141,12 @@ END;
|
||||
print("sizeof-f64: {}\n", size_of(f64));
|
||||
print("sizeof-struct: {}\n", size_of(Point));
|
||||
|
||||
// align_of
|
||||
print("alignof-u8: {}\n", align_of(u8));
|
||||
print("alignof-s32: {}\n", align_of(s32));
|
||||
print("alignof-s64: {}\n", align_of(s64));
|
||||
print("alignof-struct: {}\n", align_of(Point));
|
||||
|
||||
// type_of + category matching
|
||||
tv := 42;
|
||||
ttype := type_of(tv);
|
||||
@@ -1667,29 +1673,30 @@ END;
|
||||
|
||||
// ── GPA ─────────────────────────────────────────────────
|
||||
{
|
||||
gpa_state : GPA = .{ alloc_count = 0 };
|
||||
gpa := gpa_state.create();
|
||||
p1 := gpa.alloc(64);
|
||||
p2 := gpa.alloc(128);
|
||||
print("gpa allocs: {}\n", gpa_state.alloc_count); // gpa allocs: 2
|
||||
gpa.dealloc(p1);
|
||||
gpa.dealloc(p2);
|
||||
print("gpa final: {}\n", gpa_state.alloc_count); // gpa final: 0
|
||||
gpa := GPA.init();
|
||||
a : Allocator = xx gpa;
|
||||
p1 := a.alloc(64);
|
||||
p2 := a.alloc(128);
|
||||
print("gpa allocs: {}\n", gpa.alloc_count); // gpa allocs: 2
|
||||
a.dealloc(p1);
|
||||
a.dealloc(p2);
|
||||
print("gpa final: {}\n", gpa.alloc_count); // gpa final: 0
|
||||
}
|
||||
|
||||
// ── Arena backed by GPA (multi-chunk) ───────────────────
|
||||
{
|
||||
gpa_state3 : GPA = .{ alloc_count = 0 };
|
||||
gpa3 := gpa_state3.create();
|
||||
arena_state : Arena = ---;
|
||||
arena := arena_state.create(gpa3, 32);
|
||||
gpa3 := GPA.init();
|
||||
arena := Arena.init(xx gpa3, 32);
|
||||
a : Allocator = xx arena;
|
||||
// First chunk fits 80 usable bytes
|
||||
a1 := arena.alloc(40);
|
||||
a2 := arena.alloc(40);
|
||||
print("arena chunks: {}\n", gpa_state3.alloc_count); // arena chunks: 1
|
||||
a1 := a.alloc(40);
|
||||
a2 := a.alloc(40);
|
||||
// Counts: Arena struct itself + first chunk = 2 (Arena.init
|
||||
// allocates its own state through the parent allocator).
|
||||
print("arena chunks: {}\n", gpa3.alloc_count); // arena chunks: 2
|
||||
// Overflow → new chunk
|
||||
a3 := arena.alloc(16);
|
||||
print("arena overflow: {}\n", gpa_state3.alloc_count); // arena overflow: 2
|
||||
a3 := a.alloc(16);
|
||||
print("arena overflow: {}\n", gpa3.alloc_count); // arena overflow: 3
|
||||
// Verify memory works across chunks
|
||||
p1 : [*]u8 = xx a1;
|
||||
p3 : [*]u8 = xx a3;
|
||||
@@ -1698,27 +1705,27 @@ END;
|
||||
print("arena a1: {}\n", p1[0]); // arena a1: 42
|
||||
print("arena a3: {}\n", p3[0]); // arena a3: 99
|
||||
// Reset retains newest chunk
|
||||
arena_state.reset();
|
||||
print("arena reset idx: {}\n", arena_state.end_index); // arena reset idx: 0
|
||||
print("arena reset gpa: {}\n", gpa_state3.alloc_count);// arena reset gpa: 1
|
||||
// Deinit frees all
|
||||
arena_state.deinit();
|
||||
print("arena deinit: {}\n", gpa_state3.alloc_count); // arena deinit: 0
|
||||
arena.reset();
|
||||
print("arena reset idx: {}\n", arena.end_index); // arena reset idx: 0
|
||||
print("arena reset gpa: {}\n", gpa3.alloc_count); // arena reset gpa: 2
|
||||
// Deinit frees all chunks + the Arena state itself
|
||||
arena.deinit();
|
||||
print("arena deinit: {}\n", gpa3.alloc_count); // arena deinit: 0
|
||||
}
|
||||
|
||||
// ── BufAlloc from stack array ───────────────────────────
|
||||
{
|
||||
stack_buf : [128]u8 = ---;
|
||||
buf_state : BufAlloc = ---;
|
||||
bufalloc := buf_state.create(@stack_buf[0], 128);
|
||||
b1 := bufalloc.alloc(24);
|
||||
b2 := bufalloc.alloc(24);
|
||||
print("buf pos: {}\n", buf_state.pos); // buf pos: 48
|
||||
b3 := bufalloc.alloc(200);
|
||||
buf := BufAlloc.init(@stack_buf[0], 128);
|
||||
a : Allocator = xx buf;
|
||||
b1 := a.alloc(24);
|
||||
b2 := a.alloc(24);
|
||||
print("buf pos: {}\n", buf.pos); // buf pos: 48
|
||||
b3 := a.alloc(200);
|
||||
b3_i : s64 = xx b3;
|
||||
print("buf overflow: {}\n", b3_i); // buf overflow: 0
|
||||
buf_state.reset();
|
||||
print("buf reset: {}\n", buf_state.pos); // buf reset: 0
|
||||
buf.reset();
|
||||
print("buf reset: {}\n", buf.pos); // buf reset: 0
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2446,8 +2453,8 @@ END;
|
||||
step3 :: (a: Allocator) -> *void { a.alloc(8); }
|
||||
step2 :: (a: Allocator) -> *void { step3(a); }
|
||||
step1 :: (a: Allocator) -> *void { step2(a); }
|
||||
gpa_e6 : GPA = .{ alloc_count = 0 };
|
||||
a_e6 : Allocator = xx @gpa_e6;
|
||||
gpa_e6 := GPA.init();
|
||||
a_e6 : Allocator = xx gpa_e6;
|
||||
ptr_e6 := step1(a_e6);
|
||||
print("closure-chain-call: {}\n", ptr_e6 != null);
|
||||
a_e6.dealloc(ptr_e6);
|
||||
@@ -2498,11 +2505,9 @@ END;
|
||||
print("closure-slice: {} {} {}\n", f_a7(0), f_a7(1), f_a7(2));
|
||||
|
||||
// C5.L1: arena bulk free (closures allocated on arena, freed in bulk)
|
||||
gpa_l1 : GPA = .{ alloc_count = 0 };
|
||||
a_l1 : Allocator = xx @gpa_l1;
|
||||
arena_l1 : Arena = ---;
|
||||
arena_alloc := arena_l1.create(a_l1, 4096);
|
||||
push Context.{ allocator = arena_alloc } {
|
||||
gpa_l1 := GPA.init();
|
||||
arena_l1 := Arena.init(xx gpa_l1, 4096);
|
||||
push Context.{ allocator = xx arena_l1 } {
|
||||
n_l1 : s32 = 5;
|
||||
f_l1 := closure((x: s32) -> s32 => x + n_l1);
|
||||
print("closure-arena: {}\n", f_l1(10));
|
||||
@@ -2510,8 +2515,8 @@ END;
|
||||
arena_l1.deinit();
|
||||
|
||||
// C5.L2: GPA manual free (verify env alloc/dealloc)
|
||||
gpa_l2 : GPA = .{ alloc_count = 0 };
|
||||
a_l2 : Allocator = xx @gpa_l2;
|
||||
gpa_l2 := GPA.init();
|
||||
a_l2 : Allocator = xx gpa_l2;
|
||||
n_l2 : s32 = 7;
|
||||
result_l2 : s32 = 0;
|
||||
push Context.{ allocator = a_l2 } {
|
||||
@@ -2660,8 +2665,8 @@ END;
|
||||
print("closure-neg: {}\n", neg_fn(val_e16));
|
||||
|
||||
// C5.E17: closure with protocol value capture (#inline protocol)
|
||||
gpa_e17 : GPA = .{ alloc_count = 0 };
|
||||
a_e17 : Allocator = xx @gpa_e17;
|
||||
gpa_e17 := GPA.init();
|
||||
a_e17 : Allocator = xx gpa_e17;
|
||||
alloc_fn := closure((size: s64) -> *void => a_e17.alloc(size));
|
||||
ptr_e17 := alloc_fn(32);
|
||||
print("closure-proto-cap: {}\n", ptr_e17 != null);
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
#import "modules/allocators.sx";
|
||||
|
||||
main :: () -> void {
|
||||
arena : Arena = ---;
|
||||
arena.create(context.allocator, 4096);
|
||||
arena := Arena.init(context.allocator, 4096);
|
||||
|
||||
new_ctx := Context.{ allocator = xx @arena, data = context.data };
|
||||
new_ctx := Context.{ allocator = xx arena, data = context.data };
|
||||
push new_ctx {
|
||||
ptr := context.allocator.alloc(128);
|
||||
out("inside push\n");
|
||||
|
||||
30
examples/99-protocol-void-pointer-return.sx
Normal file
30
examples/99-protocol-void-pointer-return.sx
Normal file
@@ -0,0 +1,30 @@
|
||||
// A protocol method declared `-> *void` (literal void-pointer return,
|
||||
// NOT `Self`) returns the underlying impl's pointer to the caller
|
||||
// unchanged. The dispatch path must NOT auto-load from the result —
|
||||
// `*void` outside a `Self`-disguise is a real pointer whose pointee
|
||||
// size is unknown.
|
||||
//
|
||||
// Regression: target_type leaks from the surrounding scope (e.g. the
|
||||
// enclosing function's return type). The dispatcher used to auto-load
|
||||
// `sizeof(target_type)` bytes from every `*void` return, mistaking
|
||||
// real pointers for Self-encoded boxes. Result was that
|
||||
// `alloc.alloc(64)` through an Allocator protocol value returned the
|
||||
// first 4 bytes of malloc'd memory interpreted as `s32` (= 0 → null).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/allocators.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
gpa := GPA.init();
|
||||
alloc : Allocator = xx gpa;
|
||||
|
||||
p_direct := gpa.alloc(64);
|
||||
print("direct: null? {}\n", p_direct == null);
|
||||
|
||||
p_protocol := alloc.alloc(64);
|
||||
print("protocol: null? {}\n", p_protocol == null);
|
||||
|
||||
print("alloc_count: {}\n", gpa.alloc_count);
|
||||
|
||||
0;
|
||||
}
|
||||
31
examples/issue-0041.sx
Normal file
31
examples/issue-0041.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
// issue-0041 — Pointer / optional / array / function / tuple types as
|
||||
// expression-position values (size_of / align_of arg, const-decl RHS).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
// Unambiguous type-form const-decl aliases.
|
||||
Ptr :: *u8;
|
||||
Maybe :: ?u8;
|
||||
Arr :: [3]u8;
|
||||
Cb :: (s32) -> s32;
|
||||
|
||||
main :: () -> s32 {
|
||||
// Direct: parser fix for *T, ?T + existing [N]T path.
|
||||
print("size_of(*u8) = {}\n", size_of(*u8));
|
||||
print("align_of(*u8) = {}\n", align_of(*u8));
|
||||
print("size_of(?u8) = {}\n", size_of(?u8));
|
||||
print("size_of([3]u8) = {}\n", size_of([3]u8));
|
||||
|
||||
// Function-type literal in expression position.
|
||||
print("size_of((s32)->s32) = {}\n", size_of((s32) -> s32));
|
||||
|
||||
// Tuple literal reinterpreted as tuple type at the type-demanding site.
|
||||
print("size_of((s32, s32)) = {}\n", size_of((s32, s32)));
|
||||
|
||||
// Aliases.
|
||||
print("size_of(Ptr) = {}\n", size_of(Ptr));
|
||||
print("size_of(Maybe) = {}\n", size_of(Maybe));
|
||||
print("size_of(Arr) = {}\n", size_of(Arr));
|
||||
print("size_of(Cb) = {}\n", size_of(Cb));
|
||||
0;
|
||||
}
|
||||
22
examples/issue-0042.sx
Normal file
22
examples/issue-0042.sx
Normal file
@@ -0,0 +1,22 @@
|
||||
// issue-0042 — const-decl type alias must resolve through
|
||||
// `type_alias_map` when used as a `$T: Type` argument to size_of /
|
||||
// align_of, not silently fall back to .s64 (8 bytes).
|
||||
// Also covers identifier-RHS aliases (chains + struct aliases),
|
||||
// not just *T / [N]T / ?T forms.
|
||||
#import "modules/std.sx";
|
||||
|
||||
MyInt :: s32;
|
||||
MyChain :: MyInt;
|
||||
Wide :: struct { a: s64; b: s64; }
|
||||
WideAlias :: Wide;
|
||||
|
||||
main :: () -> s32 {
|
||||
print("direct s32: {}\n", size_of(s32));
|
||||
print("alias s32: {}\n", size_of(MyInt));
|
||||
print("chain s32: {}\n", size_of(MyChain));
|
||||
print("align alias: {}\n", align_of(MyInt));
|
||||
print("align chain: {}\n", align_of(MyChain));
|
||||
print("size struct-alias: {}\n", size_of(WideAlias));
|
||||
print("align struct-alias:{}\n", align_of(WideAlias));
|
||||
0;
|
||||
}
|
||||
Reference in New Issue
Block a user