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;
|
||||
}
|
||||
141
issues/0041-pointer-type-not-parsed-as-expression.md
Normal file
141
issues/0041-pointer-type-not-parsed-as-expression.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# issue-0041 — Pointer types don't parse as expressions / type-argument positions
|
||||
|
||||
## Symptom
|
||||
|
||||
A pointer type like `*u8` or `*void` does not parse in positions
|
||||
where a type expression is expected as a *value*, e.g.:
|
||||
|
||||
- As an argument to a `$T: Type` builtin: `size_of(*u8)`,
|
||||
`align_of(*u8)`.
|
||||
- On the RHS of a type alias: `Ptr :: *u8;`.
|
||||
|
||||
In each case the parser emits `error: unexpected token in expression`
|
||||
at the column of the `*`.
|
||||
|
||||
Pointer types DO parse correctly in dedicated type-annotation
|
||||
positions: function parameters (`(p: *u8)`), struct fields
|
||||
(`field: *u8;`), variable annotations (`p: *u8 = ...;`). So the bug
|
||||
is a parsing inconsistency between "type-annotation context" and
|
||||
"expression context where a type is expected".
|
||||
|
||||
This is pre-existing — it affects `size_of` (already shipping) and
|
||||
was just made more visible by adding `align_of` in Phase 0.6 of the
|
||||
MEM plan. Not a regression introduced by 0.6, but a real limitation
|
||||
worth pinning down because:
|
||||
|
||||
- Phase 1+ of the MEM plan will need `size_of(*T)` / `align_of(*T)`
|
||||
in user-facing allocator helpers if we want to stay terse — e.g.
|
||||
serializing a pointer-typed field in `field_value_int` patterns.
|
||||
- It's a discoverability cliff. New users WILL write `size_of(*u8)`,
|
||||
see "unexpected token", and have to learn the workaround.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
n := size_of(*u8); // error: unexpected token in expression
|
||||
print("{}\n", n);
|
||||
0;
|
||||
}
|
||||
```
|
||||
|
||||
Also fails on the alias form:
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
Ptr :: *u8; // error: unexpected token in expression
|
||||
|
||||
main :: () -> s32 { 0; }
|
||||
```
|
||||
|
||||
Both `sx run` and `sx build` reject identically.
|
||||
|
||||
## Confirmed working workarounds
|
||||
|
||||
A pointer type DOES resolve when bound through a `*void`-style
|
||||
variable type and then cast, or routed via a helper:
|
||||
|
||||
```sx
|
||||
// Workaround A: anonymous struct holding the pointer field, then
|
||||
// pull alignment from the wrapping struct (clumsy).
|
||||
Wrap :: struct { p: *u8; }
|
||||
n := align_of(Wrap); // 8 — correct for pointer alignment.
|
||||
|
||||
// Workaround B: explicit *void
|
||||
n := size_of(*void); // ALSO fails — same parse error.
|
||||
```
|
||||
|
||||
Workaround B is NOT functional — it has the same parse error. Only
|
||||
the wrap-in-struct or type-alias-via-typedef trick is currently
|
||||
viable for code that needs pointer size/alignment.
|
||||
|
||||
There is no clean way today to write `size_of(*u8)`. The whole
|
||||
class of "ptr type as type-expression value" is unsupported.
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
> Pointer types parse via a dedicated `parseTypeExpr` (or similar)
|
||||
> path that the parser invokes in type-annotation positions (param
|
||||
> lists, field declarations, variable annotations). The expression
|
||||
> grammar used in argument positions (e.g. inside `size_of(...)`)
|
||||
> dispatches through `parseExpr` instead, which treats `*` as
|
||||
> "either prefix unary deref or infix multiplication" — neither
|
||||
> matches the desired "type literal" interpretation.
|
||||
>
|
||||
> The fix likely belongs in the call-argument parser path: when
|
||||
> the callee is a builtin that takes `$T: Type`, OR more broadly
|
||||
> whenever the parser sees a `*` at the start of an expression
|
||||
> followed by an identifier that resolves to a type, it should
|
||||
> dispatch to `parseTypeExpr` instead of `parsePrefixUnary`.
|
||||
>
|
||||
> Implementation sketch:
|
||||
> - Check `src/parser.zig` for the expression entry point that
|
||||
> handles `*` prefix. Today it likely returns a `unary_op
|
||||
> { op = deref, operand = … }` AST node.
|
||||
> - Look at how lower.zig's `resolveTypeArg` consumes the AST node
|
||||
> for `size_of(s32)` — what AST shape does it expect for a type
|
||||
> literal? Probably an `identifier` whose name resolves to a type.
|
||||
> - The fix should extend `resolveTypeArg` to also accept a
|
||||
> `unary_op { op = deref, ... }` and treat it as "pointer to
|
||||
> resolved type" — equivalent to `Ptr$T` in spec terms.
|
||||
> - For the type-alias case (`Ptr :: *u8;`), the RHS of a `::`
|
||||
> const decl is parsed as an expression. The parser needs to
|
||||
> recognize that the LHS-determined shape (type-level alias)
|
||||
> should bias the RHS parser toward `parseTypeExpr`. Or: extend
|
||||
> the constant-fold path to interpret `unary_op { deref, T }` as
|
||||
> a type literal when used as a type.
|
||||
>
|
||||
> Verification:
|
||||
> 1. Add `examples/issue-0041.sx` with the repro above and
|
||||
> `tests/expected/issue-0041.txt` capturing the expected output
|
||||
> (`size_of(*u8) → 8`).
|
||||
> 2. Confirm `bash tests/run_examples.sh` still passes everything
|
||||
> else (151 tests currently).
|
||||
> 3. Run `tools/verify-step.sh` to confirm chess on three platforms.
|
||||
> 4. Also bake into `examples/50-smoke.sx` near the existing
|
||||
> `align_of` lines — add `align_of(*u8)`, `size_of(*u8)`,
|
||||
> `align_of(*void)` and regen.
|
||||
>
|
||||
> Hazard: any change to expression parsing affects a huge surface.
|
||||
> Watch for these contexts to make sure they still work post-fix:
|
||||
> - `a * b` (multiplication)
|
||||
> - `*p` (prefix deref read)
|
||||
> - `*p = …` (prefix deref write)
|
||||
> - `func(a, *b)` (deref as argument)
|
||||
> A surgical "is the next token a built-in type identifier" lookahead
|
||||
> at the `*` site is probably less invasive than a wholesale
|
||||
> type-expression-in-expression-position rewrite.
|
||||
|
||||
## Plan-level impact
|
||||
|
||||
None for Phase 0.6 — `align_of` shipped and works for every shape
|
||||
that `size_of` works for (primitives, structs, type aliases through
|
||||
non-pointer types). The 50-smoke test addition uses only
|
||||
non-pointer types, so it's stable.
|
||||
|
||||
Phase 1+ should bake an `align_of(*u8)` test once the parser fix
|
||||
lands, since the allocator API will want to round-trip pointer
|
||||
alignments at some call sites.
|
||||
@@ -0,0 +1,135 @@
|
||||
# issue-0042 — Const-decl type aliases (`MyInt :: s32;`) silently return `.s64` from `size_of` / `align_of`
|
||||
|
||||
## Symptom
|
||||
|
||||
A type alias declared via `Foo :: SomeType;` is registered in the
|
||||
lowering's `type_alias_map` but is **never consulted** when the alias
|
||||
name is later used as a type argument to `size_of` / `align_of`. The
|
||||
fallback returns `.s64` (8 bytes) — which coincidentally produces a
|
||||
correct result for any alias whose underlying type is 8 bytes
|
||||
(`*T`, `f64`, function pointers, `s64`, `u64`), silently masking the
|
||||
bug for years.
|
||||
|
||||
Observed:
|
||||
```
|
||||
size_of(s32) = 4 ← direct, correct
|
||||
size_of(MyInt) = 8 ← via alias, WRONG (expected 4)
|
||||
```
|
||||
|
||||
Where `MyInt :: s32;`.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
MyInt :: s32;
|
||||
|
||||
main :: () -> s32 {
|
||||
print("direct: {}\n", size_of(s32)); // 4
|
||||
print("alias: {}\n", size_of(MyInt)); // 8 — should be 4
|
||||
0;
|
||||
}
|
||||
```
|
||||
|
||||
`./zig-out/bin/sx run` against unmodified master prints:
|
||||
|
||||
```
|
||||
direct: 4
|
||||
alias: 8
|
||||
```
|
||||
|
||||
## Why this surfaces now
|
||||
|
||||
issue-0041 work extends the const-decl alias path to register
|
||||
pointer, optional, array, slice, many-pointer, and function-type
|
||||
aliases (`Ptr :: *u8;`, `Maybe :: ?u8;`, `Arr :: [3]u8;`,
|
||||
`Cb :: (s32) -> s32;`). Every one of those aliases ends up in
|
||||
`type_alias_map`, then `size_of(<alias>)` falls through the same
|
||||
`.identifier` branch that ignores the map — returning `.s64` (8).
|
||||
For pointer and function-type aliases this is coincidentally right
|
||||
(8 bytes). For optional, array, etc. it produces silently-wrong
|
||||
sizes (`size_of(Maybe) = 8` instead of 2;
|
||||
`size_of(Arr) = 8` instead of 3).
|
||||
|
||||
The issue-0041 work cannot land without this being fixed — the
|
||||
test snapshots would pin in the wrong values and the new feature
|
||||
would ship subtly broken.
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
> The bug lives in `src/ir/lower.zig`, in `resolveTypeArg`
|
||||
> ([line ~7132](src/ir/lower.zig#L7132)). The `.identifier`
|
||||
> branch looks like:
|
||||
>
|
||||
> ```zig
|
||||
> .identifier => |id| {
|
||||
> if (self.type_bindings) |tb| {
|
||||
> if (tb.get(id.name)) |ty| return ty;
|
||||
> }
|
||||
> const name_id = self.module.types.internString(id.name);
|
||||
> return self.module.types.findByName(name_id) orelse .s64;
|
||||
> },
|
||||
> ```
|
||||
>
|
||||
> It checks `type_bindings` (generic-monomorphization) and
|
||||
> `findByName` (registered named types), but never consults
|
||||
> `self.type_alias_map` — which is where the const-decl alias
|
||||
> registration in `lower.zig:425` puts entries. The neighbouring
|
||||
> `.type_expr` branch (line ~7143) DOES check `type_alias_map`:
|
||||
>
|
||||
> ```zig
|
||||
> .type_expr => |te| {
|
||||
> if (self.type_alias_map.get(te.name)) |alias_ty| return alias_ty;
|
||||
> return type_bridge.resolveAstType(node, &self.module.types);
|
||||
> },
|
||||
> ```
|
||||
>
|
||||
> Why two branches: an `.identifier` AST node is what parsePrimary
|
||||
> emits for non-keyword names; `.type_expr` is what it emits for
|
||||
> built-in primitive names recognised by `Type.fromName` (`s32`,
|
||||
> `u8`, etc.) and for the `f32`/`f64`/`Type` keywords. User-defined
|
||||
> alias names like `MyInt` and `Ptr` flow through `.identifier`.
|
||||
>
|
||||
> **Likely fix:** mirror the `type_alias_map.get` lookup in the
|
||||
> `.identifier` branch — try alias map first (or before/after
|
||||
> findByName, whichever is the established precedence elsewhere).
|
||||
>
|
||||
> ```zig
|
||||
> .identifier => |id| {
|
||||
> if (self.type_bindings) |tb| {
|
||||
> if (tb.get(id.name)) |ty| return ty;
|
||||
> }
|
||||
> if (self.type_alias_map.get(id.name)) |alias_ty| return alias_ty;
|
||||
> const name_id = self.module.types.internString(id.name);
|
||||
> return self.module.types.findByName(name_id) orelse .s64;
|
||||
> },
|
||||
> ```
|
||||
>
|
||||
> **Verification:**
|
||||
> 1. Add the repro above as `examples/issue-0042.sx`.
|
||||
> 2. `bash tests/run_examples.sh --update` to capture expected
|
||||
> output (`alias: 4`, not `alias: 8`).
|
||||
> 3. Make sure existing snapshots that test type aliases (search
|
||||
> `examples/` for `::` patterns followed by `size_of`) don't
|
||||
> change in unexpected ways.
|
||||
>
|
||||
> **Possible adjacency:** the issue may extend to `align_of`
|
||||
> (likely same call path) and to type-alias chains
|
||||
> (`A :: s32; B :: A;` — does B resolve through A's alias entry?).
|
||||
> Worth pinning down with a test once the primary fix lands.
|
||||
|
||||
## Plan-level impact
|
||||
|
||||
Blocks issue-0041 (compound-type-as-expression). Once 0042 is
|
||||
fixed, 0041 work can resume from the testing phase (the parser and
|
||||
lowering edits for 0041 are already in place; only the alias
|
||||
lookup is broken).
|
||||
|
||||
## Suggested fix order
|
||||
|
||||
1. Land 0042's `.identifier` alias-map lookup.
|
||||
2. Resume 0041 from the test step — re-run `examples/issue-0041.sx`
|
||||
and verify `size_of(Maybe) = 2`, `size_of(Arr) = 3`, etc.
|
||||
3. Regenerate snapshots and proceed with the 0041 finishing
|
||||
steps (50-smoke, rename, etc.).
|
||||
@@ -7,28 +7,61 @@ Allocator :: protocol #inline {
|
||||
dealloc :: (ptr: *void);
|
||||
}
|
||||
|
||||
// --- CAllocator: stateless allocator that delegates directly to libc ---
|
||||
//
|
||||
// Zero-sized struct. Used as the default `context.allocator` at program
|
||||
// start (see `__sx_default_context` in the codegen). The thunks never
|
||||
// dereference `self`, so the protocol value's ctx field is `null`.
|
||||
//
|
||||
// Unlike GPA, no `init()` is needed — there's nothing to allocate.
|
||||
|
||||
CAllocator :: struct {}
|
||||
|
||||
impl Allocator for CAllocator {
|
||||
alloc :: (self: *CAllocator, size: s64) -> *void {
|
||||
return libc_malloc(size);
|
||||
}
|
||||
dealloc :: (self: *CAllocator, ptr: *void) {
|
||||
libc_free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// --- GPA: general purpose allocator (malloc/free wrapper) ---
|
||||
//
|
||||
// Usage:
|
||||
// gpa := GPA.init(); // *GPA
|
||||
// push Context.{ allocator = xx gpa, data = null } { ... }
|
||||
// print("alloc count: {}\n", gpa.alloc_count);
|
||||
|
||||
GPA :: struct {
|
||||
alloc_count: s64;
|
||||
|
||||
create :: (gpa: *GPA) -> Allocator {
|
||||
xx gpa;
|
||||
init :: () -> *GPA {
|
||||
g : *GPA = xx libc_malloc(size_of(GPA));
|
||||
g.alloc_count = 0;
|
||||
g;
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for GPA {
|
||||
alloc :: (self: *GPA, size: s64) -> *void {
|
||||
self.alloc_count += 1;
|
||||
malloc(size);
|
||||
return libc_malloc(size);
|
||||
}
|
||||
dealloc :: (self: *GPA, ptr: *void) {
|
||||
self.alloc_count -= 1;
|
||||
free(ptr);
|
||||
libc_free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Arena: multi-chunk bump allocator ---
|
||||
//
|
||||
// Usage:
|
||||
// gpa := GPA.init();
|
||||
// arena := Arena.init(xx gpa, 4096); // *Arena
|
||||
// push Context.{ allocator = xx arena, data = null } { ... }
|
||||
// arena.reset(); // direct method on *Arena
|
||||
// arena.deinit();
|
||||
|
||||
ArenaChunk :: struct {
|
||||
next: *ArenaChunk;
|
||||
@@ -52,12 +85,13 @@ Arena :: struct {
|
||||
a.end_index = 0;
|
||||
}
|
||||
|
||||
create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator {
|
||||
a.first = null;
|
||||
a.end_index = 0;
|
||||
a.parent = parent;
|
||||
a.add_chunk(size);
|
||||
xx a;
|
||||
init :: (parent_alloc: Allocator, size: s64) -> *Arena {
|
||||
self : *Arena = xx parent_alloc.alloc(size_of(Arena));
|
||||
self.first = null;
|
||||
self.end_index = 0;
|
||||
self.parent = parent_alloc;
|
||||
self.add_chunk(size);
|
||||
self;
|
||||
}
|
||||
|
||||
reset :: (a: *Arena) {
|
||||
@@ -80,8 +114,8 @@ Arena :: struct {
|
||||
a.parent.dealloc(it);
|
||||
it = next;
|
||||
}
|
||||
a.first = null;
|
||||
a.end_index = 0;
|
||||
parent := a.parent;
|
||||
parent.dealloc(xx a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,17 +141,26 @@ impl Allocator for Arena {
|
||||
}
|
||||
|
||||
// --- BufAlloc: bump allocator backed by a user-provided slice ---
|
||||
//
|
||||
// Usage:
|
||||
// stack_buf : [128]u8 = ---;
|
||||
// buf := BufAlloc.init(@stack_buf[0], 128); // *BufAlloc
|
||||
// push Context.{ allocator = xx buf, data = null } { ... }
|
||||
// buf.reset();
|
||||
|
||||
BufAlloc :: struct {
|
||||
buf: [*]u8;
|
||||
len: s64;
|
||||
pos: s64;
|
||||
|
||||
create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator {
|
||||
b.buf = buf;
|
||||
b.len = len;
|
||||
init :: (buf: [*]u8, len: s64) -> *BufAlloc {
|
||||
self_size :: size_of(BufAlloc);
|
||||
if len < self_size { return null; }
|
||||
b : *BufAlloc = xx buf;
|
||||
b.buf = @buf[self_size];
|
||||
b.len = len - self_size;
|
||||
b.pos = 0;
|
||||
xx b;
|
||||
b;
|
||||
}
|
||||
|
||||
reset :: (b: *BufAlloc) {
|
||||
@@ -137,3 +180,66 @@ impl Allocator for BufAlloc {
|
||||
}
|
||||
dealloc :: (self: *BufAlloc, ptr: *void) {}
|
||||
}
|
||||
|
||||
// --- TrackingAllocator: wraps any Allocator, counts allocs/deallocs ---
|
||||
//
|
||||
// Useful for catching leaks during development. Wraps a parent
|
||||
// Allocator; every call delegates to the parent while updating
|
||||
// counters. `report()` prints a summary; `leak_count()` returns
|
||||
// (alloc_count - dealloc_count).
|
||||
//
|
||||
// Manual opt-in pattern (compiler auto-wrap lands in Phase 5):
|
||||
//
|
||||
// tracker := TrackingAllocator.init(context.allocator); // *TrackingAllocator
|
||||
// push Context.{ allocator = xx tracker, data = null } {
|
||||
// // ... user code allocates via tracker → delegates to the
|
||||
// // original context.allocator (libc-backed by default) ...
|
||||
// }
|
||||
// tracker.report();
|
||||
// if tracker.leak_count() != 0 { return 1; }
|
||||
//
|
||||
// Limitations under the current 2-method Allocator protocol:
|
||||
// dealloc(ptr) provides no size info, so bytes_outstanding /
|
||||
// peak_bytes cannot be tracked accurately. Only alloc count and
|
||||
// total bytes allocated are recorded. Phase 4's size-aware
|
||||
// dealloc(ptr, size, align) unlocks full byte tracking.
|
||||
|
||||
TrackingAllocator :: struct {
|
||||
parent: Allocator;
|
||||
alloc_count: s64;
|
||||
dealloc_count: s64;
|
||||
total_alloc_bytes: s64;
|
||||
|
||||
init :: (parent_alloc: Allocator) -> *TrackingAllocator {
|
||||
t : *TrackingAllocator = xx parent_alloc.alloc(size_of(TrackingAllocator));
|
||||
t.parent = parent_alloc;
|
||||
t.alloc_count = 0;
|
||||
t.dealloc_count = 0;
|
||||
t.total_alloc_bytes = 0;
|
||||
t;
|
||||
}
|
||||
|
||||
leak_count :: (t: *TrackingAllocator) -> s64 {
|
||||
t.alloc_count - t.dealloc_count;
|
||||
}
|
||||
|
||||
report :: (t: *TrackingAllocator) {
|
||||
print("TrackingAllocator: allocs={} deallocs={} outstanding={} total_alloc_bytes={}\n",
|
||||
t.alloc_count, t.dealloc_count, t.leak_count(), t.total_alloc_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for TrackingAllocator {
|
||||
alloc :: (self: *TrackingAllocator, size: s64) -> *void {
|
||||
ptr := self.parent.alloc(size);
|
||||
if ptr != null {
|
||||
self.alloc_count += 1;
|
||||
self.total_alloc_bytes += size;
|
||||
}
|
||||
ptr;
|
||||
}
|
||||
dealloc :: (self: *TrackingAllocator, ptr: *void) {
|
||||
self.parent.dealloc(ptr);
|
||||
self.dealloc_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@ out :: (str: string) -> void #builtin;
|
||||
// sin :: (x: $T) -> T #builtin;
|
||||
// cos :: (x: $T) -> T #builtin;
|
||||
size_of :: ($T: Type) -> s64 #builtin;
|
||||
malloc :: (size: s64) -> *void #builtin;
|
||||
memcpy :: (dst: *void, src: *void, size: s64) -> *void #builtin;
|
||||
memset :: (dst: *void, val: s64, size: s64) -> void #builtin;
|
||||
free :: (ptr: *void) -> void #builtin;
|
||||
align_of :: ($T: Type) -> s64 #builtin;
|
||||
// Low-level libc bindings, used by allocator implementations to avoid
|
||||
// recursing through `context.allocator`.
|
||||
libc_malloc :: (size: s64) -> *void #foreign libc "malloc";
|
||||
libc_free :: (ptr: *void) -> void #foreign libc "free";
|
||||
|
||||
malloc :: (size: s64) -> *void #foreign libc "malloc";
|
||||
memcpy :: (dst: *void, src: *void, size: s64) -> *void #foreign libc "memcpy";
|
||||
memset :: (dst: *void, val: s64, size: s64) -> void #foreign libc "memset";
|
||||
free :: (ptr: *void) -> void #foreign libc "free";
|
||||
type_of :: (val: $T) -> Type #builtin;
|
||||
type_name :: ($T: Type) -> string #builtin;
|
||||
field_count :: ($T: Type) -> s64 #builtin;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#import "std.sx";
|
||||
|
||||
assert :: (condition: bool) {
|
||||
if !condition {
|
||||
out("assertion failed\n");
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#import "modules/ui/types.sx";
|
||||
#import "modules/ui/render.sx";
|
||||
#import "modules/ui/events.sx";
|
||||
#import "modules/ui/font.sx";
|
||||
#import "modules/ui/view.sx";
|
||||
#import "modules/ui/renderer.sx";
|
||||
|
||||
@@ -17,9 +18,11 @@ UIPipeline :: struct {
|
||||
root: ViewChild;
|
||||
has_root: bool;
|
||||
|
||||
// Frame arena infrastructure
|
||||
arena_a: Arena;
|
||||
arena_b: Arena;
|
||||
// Frame arena infrastructure. Both arenas are typed `*Arena`
|
||||
// pointers (per the init-returns-typed-pointer API in
|
||||
// allocators.sx). Cast to Allocator at use sites via `xx arena_a`.
|
||||
arena_a: *Arena;
|
||||
arena_b: *Arena;
|
||||
frame_index: s64;
|
||||
body: Closure() -> View;
|
||||
has_body: bool;
|
||||
@@ -65,8 +68,8 @@ UIPipeline :: struct {
|
||||
self.has_body = true;
|
||||
self.parent_allocator = context.allocator;
|
||||
// Initialize both arenas (256KB initial, grows automatically)
|
||||
self.arena_a.create(self.parent_allocator, 262144);
|
||||
self.arena_b.create(self.parent_allocator, 262144);
|
||||
self.arena_a = Arena.init(self.parent_allocator, 262144);
|
||||
self.arena_b = Arena.init(self.parent_allocator, 262144);
|
||||
self.frame_index = 0;
|
||||
}
|
||||
|
||||
@@ -129,7 +132,7 @@ UIPipeline :: struct {
|
||||
}
|
||||
|
||||
tick_with_body :: (self: *UIPipeline) {
|
||||
build_arena : *Arena = if self.frame_index & 1 == 0 then @self.arena_a else @self.arena_b;
|
||||
build_arena : *Arena = if self.frame_index & 1 == 0 then self.arena_a else self.arena_b;
|
||||
build_arena.reset();
|
||||
|
||||
// Reset render_tree nodes (backing is stale after arena reset)
|
||||
|
||||
152
src/imports.zig
152
src/imports.zig
@@ -254,30 +254,98 @@ fn selfExePath(allocator: std.mem.Allocator) ![]const u8 {
|
||||
|
||||
/// A resolved module: the fully-resolved declarations of a single .sx file,
|
||||
/// with its own scope tracking which names are defined.
|
||||
///
|
||||
/// Imports are non-transitive. `scope` is intentionally *narrow*: it
|
||||
/// contains only the names of decls authored in THIS file (plus namespaced
|
||||
/// import aliases the file introduces). Visibility for names from
|
||||
/// flat-imported modules is computed at lookup time by joining the
|
||||
/// importer's `scope` with each direct flat-import's `scope` via
|
||||
/// `import_graph` — this lets cyclic imports (e.g. std.sx ↔ allocators.sx)
|
||||
/// resolve correctly even though one side of the cycle is skipped during
|
||||
/// `resolveImports` recursion.
|
||||
///
|
||||
/// `decls` remains the full transitive flat list so the global lowering
|
||||
/// pass can resolve a body in B that calls into C even though A never
|
||||
/// imported C directly.
|
||||
pub const ResolvedModule = struct {
|
||||
path: []const u8,
|
||||
/// Full flat decl list: own decls + every transitively-imported module's
|
||||
/// own decls (deduped by name). Walked by `lowerRoot`/`scanDecls` so
|
||||
/// transitive callees stay resolvable when their callers are lowered.
|
||||
decls: []const *Node,
|
||||
/// Decls authored in this file. What flat importers of THIS module see
|
||||
/// (their visibility BFS joins these names in via `import_graph`).
|
||||
own_decls: []const *Node,
|
||||
/// Names authored in this file (plus namespace aliases this file
|
||||
/// introduces). Used as the per-file leaf in the visibility lookup;
|
||||
/// importers do NOT splice this into their own scope — they walk the
|
||||
/// import graph at query time instead.
|
||||
scope: std.StringHashMap(void),
|
||||
|
||||
/// Try to add a declaration. Returns true if added, false if name already in scope.
|
||||
pub fn addDecl(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), decl: *Node) !bool {
|
||||
/// Add a declaration authored in this file. Updates scope + own_decls +
|
||||
/// the global flat decl list; dedups by name through `seen_list` (which
|
||||
/// already holds names previously appended via `mergeFlat`, so an
|
||||
/// authored decl that collides with a transitively-imported one stays
|
||||
/// out of the global list while still entering `own_decls` for
|
||||
/// importer-visibility purposes).
|
||||
pub fn addOwnDecl(
|
||||
self: *ResolvedModule,
|
||||
allocator: std.mem.Allocator,
|
||||
list: *std.ArrayList(*Node),
|
||||
own_list: *std.ArrayList(*Node),
|
||||
seen_list: *std.StringHashMap(void),
|
||||
decl: *Node,
|
||||
) !bool {
|
||||
var append_to_global = true;
|
||||
if (decl.data.declName()) |name| {
|
||||
if (self.scope.contains(name)) return false;
|
||||
try self.scope.put(name, {});
|
||||
if (seen_list.contains(name)) {
|
||||
append_to_global = false;
|
||||
} else {
|
||||
try seen_list.put(name, {});
|
||||
}
|
||||
}
|
||||
try list.append(allocator, decl);
|
||||
if (append_to_global) try list.append(allocator, decl);
|
||||
try own_list.append(allocator, decl);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Merge another module's decls as flat imports (skipping duplicates).
|
||||
pub fn mergeFlat(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), other: ResolvedModule) !void {
|
||||
/// Flat-import another module. The imported names are NOT added to
|
||||
/// `self.scope` — visibility joins per-file scopes at lookup time via
|
||||
/// `import_graph`. We only need to append `other.decls` (the full
|
||||
/// transitive list) to the global `list` so the lowering pass can
|
||||
/// still resolve transitively-imported callees. Deduped by name.
|
||||
pub fn mergeFlat(
|
||||
self: *ResolvedModule,
|
||||
allocator: std.mem.Allocator,
|
||||
list: *std.ArrayList(*Node),
|
||||
seen_list: *std.StringHashMap(void),
|
||||
other: ResolvedModule,
|
||||
) !void {
|
||||
_ = self;
|
||||
for (other.decls) |decl| {
|
||||
_ = try self.addDecl(allocator, list, decl);
|
||||
if (decl.data.declName()) |name| {
|
||||
if (seen_list.contains(name)) continue;
|
||||
try seen_list.put(name, {});
|
||||
}
|
||||
try list.append(allocator, decl);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add another module as a namespaced import.
|
||||
pub fn addNamespace(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), name: []const u8, other: ResolvedModule, span: ast.Span) !void {
|
||||
/// Add another module as a namespaced import. The alias `name` becomes
|
||||
/// part of this module's own decls (so a flat-importer of this module
|
||||
/// sees the alias one hop out — matching authored names).
|
||||
pub fn addNamespace(
|
||||
self: *ResolvedModule,
|
||||
allocator: std.mem.Allocator,
|
||||
list: *std.ArrayList(*Node),
|
||||
own_list: *std.ArrayList(*Node),
|
||||
seen_list: *std.StringHashMap(void),
|
||||
name: []const u8,
|
||||
other: ResolvedModule,
|
||||
span: ast.Span,
|
||||
) !void {
|
||||
const ns_node = try allocator.create(Node);
|
||||
ns_node.* = .{
|
||||
.span = span,
|
||||
@@ -287,11 +355,19 @@ pub const ResolvedModule = struct {
|
||||
} },
|
||||
};
|
||||
try self.scope.put(name, {});
|
||||
try seen_list.put(name, {});
|
||||
try list.append(allocator, ns_node);
|
||||
try own_list.append(allocator, ns_node);
|
||||
}
|
||||
|
||||
pub fn finalize(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node)) !void {
|
||||
pub fn finalize(
|
||||
self: *ResolvedModule,
|
||||
allocator: std.mem.Allocator,
|
||||
list: *std.ArrayList(*Node),
|
||||
own_list: *std.ArrayList(*Node),
|
||||
) !void {
|
||||
self.decls = try list.toOwnedSlice(allocator);
|
||||
self.own_decls = try own_list.toOwnedSlice(allocator);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -323,6 +399,7 @@ pub fn resolveImports(
|
||||
var mod = ResolvedModule{
|
||||
.path = file_path,
|
||||
.decls = &.{},
|
||||
.own_decls = &.{},
|
||||
.scope = std.StringHashMap(void).init(allocator),
|
||||
};
|
||||
|
||||
@@ -338,6 +415,11 @@ pub fn resolveImports(
|
||||
const flat_decls = try flattenComptimeConditionals(allocator, root.data.root.decls, comptime_ctx);
|
||||
|
||||
var decl_list = std.ArrayList(*Node).empty;
|
||||
var own_decl_list = std.ArrayList(*Node).empty;
|
||||
// Name set spanning every decl already appended to `decl_list` — used
|
||||
// by `mergeFlat` to dedupe across diamond imports now that `mod.scope`
|
||||
// is non-transitive and can no longer serve as the dedup key.
|
||||
var seen_in_list = std.StringHashMap(void).init(allocator);
|
||||
|
||||
for (flat_decls) |decl| {
|
||||
if (decl.data == .c_import_decl) {
|
||||
@@ -397,21 +479,23 @@ pub fn resolveImports(
|
||||
};
|
||||
ns_node.source_file = file_path;
|
||||
try mod.scope.put(ns_name, {});
|
||||
try seen_in_list.put(ns_name, {});
|
||||
try decl_list.append(allocator, ns_node);
|
||||
try own_decl_list.append(allocator, ns_node);
|
||||
} else {
|
||||
// Flat: add fn_decls directly + keep c_import_decl
|
||||
for (result.fn_decls) |fd| {
|
||||
fd.source_file = file_path;
|
||||
_ = try mod.addDecl(allocator, &decl_list, fd);
|
||||
_ = try mod.addOwnDecl(allocator, &decl_list, &own_decl_list, &seen_in_list, fd);
|
||||
}
|
||||
decl.source_file = file_path;
|
||||
_ = try mod.addDecl(allocator, &decl_list, decl);
|
||||
_ = try mod.addOwnDecl(allocator, &decl_list, &own_decl_list, &seen_in_list, decl);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (decl.data != .import_decl) {
|
||||
decl.source_file = file_path;
|
||||
_ = try mod.addDecl(allocator, &decl_list, decl);
|
||||
_ = try mod.addOwnDecl(allocator, &decl_list, &own_decl_list, &seen_in_list, decl);
|
||||
continue;
|
||||
}
|
||||
const imp = decl.data.import_decl;
|
||||
@@ -473,13 +557,13 @@ pub fn resolveImports(
|
||||
};
|
||||
|
||||
if (imp.name) |ns_name| {
|
||||
try mod.addNamespace(allocator, &decl_list, ns_name, imported_mod, decl.span);
|
||||
try mod.addNamespace(allocator, &decl_list, &own_decl_list, &seen_in_list, ns_name, imported_mod, decl.span);
|
||||
} else {
|
||||
try mod.mergeFlat(allocator, &decl_list, imported_mod);
|
||||
try mod.mergeFlat(allocator, &decl_list, &seen_in_list, imported_mod);
|
||||
}
|
||||
}
|
||||
|
||||
try mod.finalize(allocator, &decl_list);
|
||||
try mod.finalize(allocator, &decl_list, &own_decl_list);
|
||||
return mod;
|
||||
}
|
||||
|
||||
@@ -524,13 +608,20 @@ fn resolveDirectoryImport(
|
||||
try chain.put(dir_path, {});
|
||||
defer _ = chain.remove(dir_path);
|
||||
|
||||
// Merge all files into a combined module
|
||||
// Merge all files into a combined module. From an importer's perspective
|
||||
// a directory is one big module: the combined module's `own_decls` is
|
||||
// the union of every file's `own_decls`, so flat-importing the directory
|
||||
// exposes everything the files themselves authored — but not what those
|
||||
// files transitively imported from outside the directory.
|
||||
var combined = ResolvedModule{
|
||||
.path = dir_path,
|
||||
.decls = &.{},
|
||||
.own_decls = &.{},
|
||||
.scope = std.StringHashMap(void).init(allocator),
|
||||
};
|
||||
var decl_list = std.ArrayList(*Node).empty;
|
||||
var own_decl_list = std.ArrayList(*Node).empty;
|
||||
var seen_in_list = std.StringHashMap(void).init(allocator);
|
||||
|
||||
for (file_names.items) |file_name| {
|
||||
const file_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ dir_path, file_name });
|
||||
@@ -568,9 +659,34 @@ fn resolveDirectoryImport(
|
||||
break :file_blk result;
|
||||
};
|
||||
|
||||
try combined.mergeFlat(allocator, &decl_list, file_mod);
|
||||
// Source-order matters: a file's own decls (e.g. `impl Foo` blocks)
|
||||
// may reference types defined in OTHER files that THIS file imports.
|
||||
// `file_mod.decls` already lists transitive-imported decls before
|
||||
// the file's own decls (resolveImports processes `#import` lines in
|
||||
// source order, and #imports usually come first), so iterating it
|
||||
// directly preserves the scan order the lowering pass needs to
|
||||
// register `Event` (a tagged_union) before `handle_event(e: *Event)`
|
||||
// triggers the placeholder-struct fallback in `resolveTypeName`.
|
||||
for (file_mod.decls) |decl| {
|
||||
if (decl.data.declName()) |name| {
|
||||
if (seen_in_list.contains(name)) continue;
|
||||
try seen_in_list.put(name, {});
|
||||
}
|
||||
try decl_list.append(allocator, decl);
|
||||
}
|
||||
// Separately track which decls the directory `re-exports` to its
|
||||
// flat-importers. Position in `own_decl_list` doesn't matter — it's
|
||||
// only consumed by the importer-side visibility join (`isNameVisible`
|
||||
// in lower.zig) which treats it as a set.
|
||||
for (file_mod.own_decls) |decl| {
|
||||
if (decl.data.declName()) |name| {
|
||||
if (combined.scope.contains(name)) continue;
|
||||
try combined.scope.put(name, {});
|
||||
}
|
||||
try own_decl_list.append(allocator, decl);
|
||||
}
|
||||
}
|
||||
|
||||
try combined.finalize(allocator, &decl_list);
|
||||
try combined.finalize(allocator, &decl_list, &own_decl_list);
|
||||
return combined;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -352,11 +352,8 @@ pub const BuiltinId = enum(u16) {
|
||||
cos,
|
||||
floor,
|
||||
size_of,
|
||||
align_of,
|
||||
cast,
|
||||
malloc,
|
||||
free,
|
||||
memcpy,
|
||||
memset,
|
||||
type_of,
|
||||
alloc,
|
||||
dealloc,
|
||||
@@ -519,5 +516,10 @@ pub const ConstantValue = union(enum) {
|
||||
aggregate: []const ConstantValue,
|
||||
/// Vtable constant: struct of function pointers, used for protocol vtable globals.
|
||||
vtable: []const FuncId,
|
||||
/// Function pointer leaf, for static initializers that include
|
||||
/// function addresses inside nested aggregates (e.g. the inline
|
||||
/// Allocator value `{ ctx, alloc_fn, dealloc_fn }` for the
|
||||
/// process-wide default Context).
|
||||
func_ref: FuncId,
|
||||
};
|
||||
|
||||
|
||||
@@ -238,8 +238,11 @@ pub const Interpreter = struct {
|
||||
.boolean => |b| @intFromBool(b),
|
||||
.null_val => 0,
|
||||
.heap_ptr => |hp| blk: {
|
||||
const mem = self.heapSlice(hp) orelse return error.TypeError;
|
||||
break :blk @intFromPtr(mem.ptr) + hp.offset;
|
||||
// `heapSlice` returns the slice already advanced by `hp.offset`,
|
||||
// so its `.ptr` IS the offset address. Adding `hp.offset` again
|
||||
// double-counts and lands the foreign call past the buffer end.
|
||||
_ = self.heapSlice(hp) orelse return error.TypeError;
|
||||
break :blk @intFromPtr(self.heap.items[hp.id].ptr) + hp.offset;
|
||||
},
|
||||
.string => |s| blk: {
|
||||
const buf = try self.alloc.alloc(u8, s.len + 1);
|
||||
@@ -1315,6 +1318,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
return .{ .aggregate = fields };
|
||||
},
|
||||
.func_ref => |fid| .{ .func_ref = fid },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1401,56 +1405,6 @@ pub const Interpreter = struct {
|
||||
|
||||
fn execBuiltinInner(self: *Interpreter, bi: inst_mod.BuiltinCall, frame: *Frame) InterpError!ExecResult {
|
||||
switch (bi.builtin) {
|
||||
.malloc => {
|
||||
const size_val = frame.getRef(bi.args[0]);
|
||||
const size: usize = @intCast(size_val.asInt() orelse return error.TypeError);
|
||||
const hp = self.heapAlloc(size);
|
||||
return .{ .value = .{ .heap_ptr = hp } };
|
||||
},
|
||||
.free => {
|
||||
const ptr = frame.getRef(bi.args[0]);
|
||||
switch (ptr) {
|
||||
.heap_ptr => |hp| self.heapFree(hp),
|
||||
else => {},
|
||||
}
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.memcpy => {
|
||||
const dst = frame.getRef(bi.args[0]);
|
||||
const src = frame.getRef(bi.args[1]);
|
||||
const len_val = frame.getRef(bi.args[2]);
|
||||
const len: usize = @intCast(len_val.asInt() orelse return error.TypeError);
|
||||
const dst_hp = switch (dst) {
|
||||
.heap_ptr => |hp| hp,
|
||||
else => return error.CannotEvalComptime,
|
||||
};
|
||||
const src_bytes: []const u8 = switch (src) {
|
||||
.heap_ptr => |hp| self.heapSlice(hp) orelse return error.CannotEvalComptime,
|
||||
.string => |s| s,
|
||||
// Raw host address (e.g. a `*u8` returned by a foreign
|
||||
// call like getenv). Read `len` bytes across the FFI
|
||||
// boundary into the sx-managed dst.
|
||||
.int => |addr| blk: {
|
||||
const raw: [*]const u8 = @ptrFromInt(@as(usize, @bitCast(addr)));
|
||||
break :blk raw[0..len];
|
||||
},
|
||||
else => return error.CannotEvalComptime,
|
||||
};
|
||||
self.heapMemcpy(dst_hp, src_bytes, len);
|
||||
return .{ .value = .{ .heap_ptr = dst_hp } };
|
||||
},
|
||||
.memset => {
|
||||
const dst = frame.getRef(bi.args[0]);
|
||||
const val = frame.getRef(bi.args[1]);
|
||||
const len_val = frame.getRef(bi.args[2]);
|
||||
const byte: u8 = @intCast(@as(u64, @bitCast(val.asInt() orelse return error.TypeError)) & 0xFF);
|
||||
const len: usize = @intCast(len_val.asInt() orelse return error.TypeError);
|
||||
switch (dst) {
|
||||
.heap_ptr => |hp| self.heapMemset(hp, byte, len),
|
||||
else => {},
|
||||
}
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.out => {
|
||||
const str_val = frame.getRef(bi.args[0]);
|
||||
if (str_val.asString(self)) |s| {
|
||||
@@ -1462,6 +1416,9 @@ pub const Interpreter = struct {
|
||||
// Return a default size (8 bytes for most types)
|
||||
return .{ .value = .{ .int = 8 } };
|
||||
},
|
||||
.align_of => {
|
||||
return .{ .value = .{ .int = 8 } };
|
||||
},
|
||||
.sqrt => {
|
||||
const val = frame.getRef(bi.args[0]);
|
||||
const f = val.asFloat() orelse return error.TypeError;
|
||||
|
||||
481
src/ir/lower.zig
481
src/ir/lower.zig
@@ -161,6 +161,10 @@ pub const Lowering = struct {
|
||||
name: []const u8,
|
||||
param_types: []const TypeId, // excluding self
|
||||
ret_type: TypeId,
|
||||
// True when the AST return type was `Self` (encoded here as *void).
|
||||
// Lets the dispatcher distinguish Self-disguised-as-*void (auto-unbox
|
||||
// on the caller side) from a literal `-> *void` (return as-is).
|
||||
ret_is_self: bool = false,
|
||||
};
|
||||
|
||||
/// One impl block for a parameterised protocol (e.g. `impl Into(Block) for Closure() -> void`).
|
||||
@@ -214,6 +218,12 @@ pub const Lowering = struct {
|
||||
self.scanDecls(decls);
|
||||
// Pass 1b: inject compile-time constants (OS, ARCH, POINTER_SIZE) from target config
|
||||
self.injectComptimeConstants();
|
||||
// Pass 1c: emit the process-wide default Context global, statically
|
||||
// initialised to a CAllocator-backed Allocator value. Used by FFI
|
||||
// wrappers in Step 4 and by the interp's `callWithDefaultContext`
|
||||
// entry. Only fires when the program imports `std.sx` (so Context +
|
||||
// Allocator + CAllocator are all registered).
|
||||
self.emitDefaultContextGlobal();
|
||||
// Pass 2: lower main (and comptime side-effects)
|
||||
self.lowerMainAndComptime(decls);
|
||||
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
|
||||
@@ -418,10 +428,32 @@ pub const Lowering = struct {
|
||||
} else if (cd.value.data == .union_decl) {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
} else if (cd.value.data == .type_expr) {
|
||||
// Type alias: MyFloat :: f64; → register MyFloat as alias for f64
|
||||
} else if (cd.value.data == .type_expr or
|
||||
cd.value.data == .pointer_type_expr or
|
||||
cd.value.data == .many_pointer_type_expr or
|
||||
cd.value.data == .array_type_expr or
|
||||
cd.value.data == .slice_type_expr or
|
||||
cd.value.data == .optional_type_expr or
|
||||
cd.value.data == .function_type_expr)
|
||||
{
|
||||
// Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (s32) -> s32;
|
||||
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||
self.type_alias_map.put(cd.name, target_ty) catch {};
|
||||
} else if (cd.value.data == .identifier) {
|
||||
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide;
|
||||
// Chase through type_alias_map, then look up named types
|
||||
// in the table. Forward references resolve lazily because
|
||||
// the .identifier branch of resolveTypeArg also consults
|
||||
// type_alias_map at use time.
|
||||
const rhs_name = cd.value.data.identifier.name;
|
||||
if (self.type_alias_map.get(rhs_name)) |chained| {
|
||||
self.type_alias_map.put(cd.name, chained) catch {};
|
||||
} else {
|
||||
const name_id = self.module.types.internString(rhs_name);
|
||||
if (self.module.types.findByName(name_id)) |tid| {
|
||||
self.type_alias_map.put(cd.name, tid) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle generic struct instantiation: Vec3 :: Vec(3, f32)
|
||||
// Parser produces a .call node for these (not parameterized_type_expr)
|
||||
@@ -723,11 +755,38 @@ pub const Lowering = struct {
|
||||
// Only restrict C import fn_decls: foreign_expr with no library_ref
|
||||
if (fd.body.data != .foreign_expr) return true;
|
||||
if (fd.body.data.foreign_expr.library_ref != null) return true;
|
||||
// It's a C import fn_decl — check module scope
|
||||
return self.isNameVisible(fn_name);
|
||||
}
|
||||
|
||||
/// Non-transitive `#import` visibility check for top-level decls.
|
||||
///
|
||||
/// `module_scopes[F]` holds ONLY the names authored in file F (plus its
|
||||
/// namespace aliases). Cross-module visibility is joined here at query
|
||||
/// time by walking each direct flat-import edge in `import_graph` — a
|
||||
/// name is visible from F when it's authored in F or in any module F
|
||||
/// directly `#import`s. Doing the join here (instead of pre-merging in
|
||||
/// `resolveImports`) lets cyclic imports like std.sx ↔ allocators.sx
|
||||
/// still resolve, since the cycle's skipped edge is still recorded in
|
||||
/// `import_graph` and the partner's scope is filled in by the time
|
||||
/// lowering queries it.
|
||||
///
|
||||
/// Falls open when the scoping infrastructure isn't wired (comptime
|
||||
/// callers, directory imports without main_file, etc.). The caller is
|
||||
/// responsible for restricting the call to names that ARE known
|
||||
/// top-level decls; otherwise every local variable would be policed.
|
||||
fn isNameVisible(self: *Lowering, name: []const u8) bool {
|
||||
const scopes = self.module_scopes orelse return true;
|
||||
const source = self.current_source_file orelse return true;
|
||||
const scope = scopes.get(source) orelse return true;
|
||||
return scope.contains(fn_name);
|
||||
const own_scope = scopes.get(source) orelse return true;
|
||||
if (own_scope.contains(name)) return true;
|
||||
const graph = self.import_graph orelse return true;
|
||||
const direct = graph.get(source) orelse return true;
|
||||
var it = direct.iterator();
|
||||
while (it.next()) |kv| {
|
||||
const dep = scopes.get(kv.key_ptr.*) orelse continue;
|
||||
if (dep.contains(name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Lazily lower a function body on demand. Called when lowerCall can't find
|
||||
@@ -737,8 +796,21 @@ pub const Lowering = struct {
|
||||
if (self.lowered_functions.contains(name)) return;
|
||||
// No AST? (builtins, foreign functions, or imported functions not in this file)
|
||||
const fd = self.fn_ast_map.get(name) orelse return;
|
||||
// Check builtin/foreign/generic — these stay as extern stubs
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr or fd.body.data == .compiler_expr) return;
|
||||
// Foreign declarations stay as extern stubs but need to be REGISTERED
|
||||
// in the current module so callers get a real FuncId. Without this,
|
||||
// a comptime-lowered function (e.g. `concat` from std.sx pulled into
|
||||
// a fresh ct_module via `evalComptimeString`) emits `.call` against a
|
||||
// FuncId that doesn't exist locally; the interp can't find the
|
||||
// foreign target and silently no-ops instead of dispatching to libc.
|
||||
if (fd.body.data == .foreign_expr) {
|
||||
if (self.resolveFuncByName(name) == null) {
|
||||
self.declareFunction(fd, name);
|
||||
self.lowered_functions.put(name, {}) catch {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Builtins / #compiler bodies stay as compiler-handled — no extern stub needed.
|
||||
if (fd.body.data == .builtin_expr or fd.body.data == .compiler_expr) return;
|
||||
if (fd.type_params.len > 0) return; // generics handled by monomorphization (Step 3.13)
|
||||
|
||||
// Defer functions with type-category matches until all types are registered.
|
||||
@@ -1715,12 +1787,29 @@ pub const Lowering = struct {
|
||||
}
|
||||
// Check module-level value constants (e.g. AF_INET :s32: 2)
|
||||
if (self.module_const_map.get(id.name)) |ci| {
|
||||
if (!self.isNameVisible(id.name)) {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, node.span, "'{s}' is not visible; #import the module that declares it", .{id.name});
|
||||
break :blk self.emitError(id.name, node.span);
|
||||
}
|
||||
break :blk self.emitModuleConst(ci);
|
||||
}
|
||||
// Check if it's a function name — produce function pointer reference
|
||||
// Resolve mangled name for block-local functions
|
||||
const eff_fn_name = if (self.scope) |scope| scope.lookupFn(id.name) orelse id.name else id.name;
|
||||
if (self.fn_ast_map.contains(eff_fn_name)) {
|
||||
// Visibility check only for user-typed bare names (id.name
|
||||
// == eff_fn_name) without a UFCS alias. Mangled local-
|
||||
// scope names and UFCS rewrites are compiler indirections
|
||||
// and stay exempt.
|
||||
if (std.mem.eql(u8, eff_fn_name, id.name) and
|
||||
self.ufcs_alias_map.get(id.name) == null and
|
||||
!self.isNameVisible(eff_fn_name))
|
||||
{
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, node.span, "'{s}' is not visible; #import the module that declares it", .{eff_fn_name});
|
||||
break :blk self.emitError(eff_fn_name, node.span);
|
||||
}
|
||||
// Type-as-value: if target is Any (Type variable), produce a type name string
|
||||
if (self.target_type == .any) {
|
||||
const fd = self.fn_ast_map.get(eff_fn_name).?;
|
||||
@@ -4423,6 +4512,19 @@ pub const Lowering = struct {
|
||||
d.addFmt(.err, c.callee.span, "C function '{s}' not visible; add #import for the module that declares it", .{eff_name});
|
||||
return Ref.none;
|
||||
}
|
||||
// Non-transitive `#import` visibility check. Apply only when the
|
||||
// user-typed name resolved as-is to a top-level fn — local-scope
|
||||
// mangling (eff_name != id_name) and UFCS alias rewriting are
|
||||
// compiler indirections and stay exempt.
|
||||
if (std.mem.eql(u8, eff_name, id_name) and
|
||||
self.ufcs_alias_map.get(id_name) == null and
|
||||
self.fn_ast_map.contains(eff_name) and
|
||||
!self.isNameVisible(eff_name))
|
||||
{
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, c.callee.span, "'{s}' is not visible; #import the module that declares it", .{eff_name});
|
||||
return Ref.none;
|
||||
}
|
||||
if (self.fn_ast_map.get(eff_name)) |fd| {
|
||||
if (self.current_match_tags) |tags| {
|
||||
if (tags.len > 0 and self.hasCastWithRuntimeType(c)) {
|
||||
@@ -4608,19 +4710,8 @@ pub const Lowering = struct {
|
||||
}
|
||||
// Check builtins first (these are handled natively by interpreter and emitter)
|
||||
if (resolveBuiltin(id.name)) |bid| {
|
||||
// free(protocol_value) → extract ctx (field 0) and free it
|
||||
if (bid == .free and args.items.len == 1) {
|
||||
const arg_ty = self.builder.getRefType(args.items[0]);
|
||||
if (self.getProtocolInfo(arg_ty) != null) {
|
||||
const void_ptr_ty = self.module.types.ptrTo(.void);
|
||||
const ctx_ref = self.builder.emit(.{ .struct_get = .{ .base = args.items[0], .field_index = 0 } }, void_ptr_ty);
|
||||
return self.builder.emit(.{ .heap_free = .{ .operand = ctx_ref } }, .void);
|
||||
}
|
||||
}
|
||||
const ret_ty: TypeId = switch (bid) {
|
||||
.malloc => .s64, // pointer
|
||||
.size_of => .s64,
|
||||
.memcpy, .memset => .s64,
|
||||
.size_of, .align_of => .s64,
|
||||
.sqrt, .sin, .cos, .floor => blk: {
|
||||
// Math builtins: return type matches argument type ($T -> T)
|
||||
if (c.args.len > 0) {
|
||||
@@ -5041,6 +5132,53 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Generic method on a non-template struct: `obj.method($T, ...)`
|
||||
// or inferred form `obj.method(val)` where val's type pins $T.
|
||||
if (self.fn_ast_map.get(qualified)) |gen_fd| {
|
||||
if (gen_fd.type_params.len > 0 and gen_fd.body.data != .compiler_expr) {
|
||||
// Effective AST args: prepend receiver so positions
|
||||
// line up with fd.params (which has self at index 0).
|
||||
var eff_args = std.ArrayList(*const Node).empty;
|
||||
defer eff_args.deinit(self.alloc);
|
||||
eff_args.append(self.alloc, effective_obj_node) catch unreachable;
|
||||
for (c.args) |a| eff_args.append(self.alloc, a) catch unreachable;
|
||||
|
||||
var gbindings = self.buildTypeBindings(gen_fd, eff_args.items);
|
||||
defer gbindings.deinit();
|
||||
|
||||
const gmangled = self.mangleGenericName(qualified, gen_fd, &gbindings);
|
||||
if (!self.lowered_functions.contains(gmangled)) {
|
||||
self.monomorphizeFunction(gen_fd, gmangled, &gbindings);
|
||||
}
|
||||
if (self.resolveFuncByName(gmangled)) |gfid| {
|
||||
const gfunc = &self.module.functions.items[@intFromEnum(gfid)];
|
||||
const gret_ty = gfunc.ret;
|
||||
const gparams = gfunc.params;
|
||||
// Strip type-decl slots from method_args. method_args[0] is the
|
||||
// receiver (corresponds to fd.params[0] = self, never a type decl).
|
||||
// Walk fd.params[1..], advance arg_idx through method_args[1..].
|
||||
var gvalue_args = std.ArrayList(Ref).empty;
|
||||
defer gvalue_args.deinit(self.alloc);
|
||||
gvalue_args.append(self.alloc, method_args.items[0]) catch unreachable;
|
||||
const types_explicit = method_args.items.len == gen_fd.params.len;
|
||||
var arg_idx: usize = 1;
|
||||
for (gen_fd.params[1..]) |p| {
|
||||
if (isTypeParamDecl(&p, gen_fd.type_params)) {
|
||||
if (types_explicit) arg_idx += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg_idx < method_args.items.len) {
|
||||
gvalue_args.append(self.alloc, method_args.items[arg_idx]) catch unreachable;
|
||||
}
|
||||
arg_idx += 1;
|
||||
}
|
||||
self.fixupMethodReceiver(&gvalue_args, gfunc, effective_obj_node, obj_ty);
|
||||
self.coerceCallArgs(gvalue_args.items, gparams);
|
||||
return self.builder.call(gfid, gvalue_args.items, gret_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try non-generic qualified method
|
||||
if (self.fn_ast_map.get(qualified)) |fd| {
|
||||
if (!self.lowered_functions.contains(qualified)) {
|
||||
@@ -5134,6 +5272,49 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit `context.allocator.alloc(size)` dispatch — used by internal
|
||||
/// compiler-driven heap copies (e.g. the `xx value` protocol-erasure
|
||||
/// path in `buildProtocolValue`). Routes through whatever allocator is
|
||||
/// currently installed in `context`, so a surrounding
|
||||
/// `push Context.{ allocator = my_alloc, ... }` actually backs every
|
||||
/// allocation including the ones the compiler inserts.
|
||||
///
|
||||
/// Falls back to `.heap_alloc` (libc malloc) only when the `context`
|
||||
/// global hasn't been registered (programs that don't `#import
|
||||
/// "modules/std.sx"`). All standard sx code imports std.sx via
|
||||
/// allocators.sx, so the fallback exists strictly for the bootstrapping
|
||||
/// edge case.
|
||||
fn allocViaContext(self: *Lowering, size_ref: Ref, void_ptr_ty: TypeId) Ref {
|
||||
const ctx_gi = self.global_names.get("context") orelse {
|
||||
return self.builder.emit(.{ .heap_alloc = .{ .operand = size_ref } }, void_ptr_ty);
|
||||
};
|
||||
const ctx_ty_info = self.module.types.get(ctx_gi.ty);
|
||||
if (ctx_ty_info != .@"struct" or ctx_ty_info.@"struct".fields.len < 1) {
|
||||
return self.builder.emit(.{ .heap_alloc = .{ .operand = size_ref } }, void_ptr_ty);
|
||||
}
|
||||
const allocator_ty = ctx_ty_info.@"struct".fields[0].ty;
|
||||
const ctx = self.builder.emit(.{ .global_get = ctx_gi.id }, ctx_gi.ty);
|
||||
const allocator = self.builder.structGet(ctx, 0, allocator_ty);
|
||||
// #inline Allocator protocol layout: { ctx, alloc_fn_ptr, dealloc_fn_ptr }.
|
||||
// field 0 = receiver ctx, field 1 = alloc fn-ptr.
|
||||
const alloc_ctx = self.builder.structGet(allocator, 0, void_ptr_ty);
|
||||
const fn_ptr = self.builder.structGet(allocator, 1, void_ptr_ty);
|
||||
const args = self.alloc.dupe(Ref, &.{ alloc_ctx, size_ref }) catch unreachable;
|
||||
return self.builder.emit(.{ .call_indirect = .{
|
||||
.callee = fn_ptr,
|
||||
.args = args,
|
||||
} }, void_ptr_ty);
|
||||
}
|
||||
|
||||
/// Emit a call to a foreign-declared function looked up by name.
|
||||
/// Used for the compiler-internal byte-copy in the protocol-erasure
|
||||
/// heap path and the closure env-copy path, both of which need
|
||||
/// libc `memcpy` after the `#builtin` form was dropped.
|
||||
fn callForeign(self: *Lowering, name: []const u8, args: []const Ref, ret_ty: TypeId) Ref {
|
||||
const fid = self.resolveFuncByName(name) orelse @panic("foreign symbol missing — std.sx not imported?");
|
||||
return self.builder.call(fid, args, ret_ty);
|
||||
}
|
||||
|
||||
/// Pattern-match `context.allocator.alloc(size)` → heap_alloc,
|
||||
/// `context.allocator.dealloc(ptr)` → heap_free.
|
||||
fn matchContextAllocCall(self: *Lowering, fa: ast.FieldAccess, call_args: []const Ref) ?Ref {
|
||||
@@ -5177,11 +5358,8 @@ pub const Lowering = struct {
|
||||
.{ "cos", inst_mod.BuiltinId.cos },
|
||||
.{ "floor", inst_mod.BuiltinId.floor },
|
||||
.{ "size_of", inst_mod.BuiltinId.size_of },
|
||||
.{ "align_of", inst_mod.BuiltinId.align_of },
|
||||
.{ "cast", inst_mod.BuiltinId.cast },
|
||||
.{ "malloc", inst_mod.BuiltinId.malloc },
|
||||
.{ "free", inst_mod.BuiltinId.free },
|
||||
.{ "memcpy", inst_mod.BuiltinId.memcpy },
|
||||
.{ "memset", inst_mod.BuiltinId.memset },
|
||||
};
|
||||
inline for (builtins) |entry| {
|
||||
if (std.mem.eql(u8, name, entry[0])) return entry[1];
|
||||
@@ -5349,11 +5527,7 @@ pub const Lowering = struct {
|
||||
const env_byte_size_inner = self.computeEnvSize(capture_list);
|
||||
const env_size_val = self.builder.constInt(@intCast(env_byte_size_inner), .s64);
|
||||
// memcpy(local_alloca, env_param, size)
|
||||
const cp_args = self.alloc.dupe(Ref, &.{ env_local, env_param_ref, env_size_val }) catch unreachable;
|
||||
_ = self.builder.emit(.{ .call_builtin = .{
|
||||
.builtin = inst_mod.BuiltinId.memcpy,
|
||||
.args = cp_args,
|
||||
} }, self.module.types.ptrTo(.void));
|
||||
_ = self.callForeign("memcpy", &.{ env_local, env_param_ref, env_size_val }, self.module.types.ptrTo(.void));
|
||||
|
||||
for (capture_list, 0..) |cap, i| {
|
||||
// GEP into env struct to get field pointer
|
||||
@@ -5440,11 +5614,7 @@ pub const Lowering = struct {
|
||||
const ptr_void = self.module.types.ptrTo(.void);
|
||||
const env_heap = self.builder.emit(.{ .heap_alloc = .{ .operand = env_size } }, ptr_void);
|
||||
// memcpy(heap, stack_alloca, size)
|
||||
const args = self.alloc.dupe(Ref, &.{ env_heap, env_local, env_size }) catch unreachable;
|
||||
_ = self.builder.emit(.{ .call_builtin = .{
|
||||
.builtin = inst_mod.BuiltinId.memcpy,
|
||||
.args = args,
|
||||
} }, ptr_void);
|
||||
_ = self.callForeign("memcpy", &.{ env_heap, env_local, env_size }, ptr_void);
|
||||
|
||||
return self.builder.closureCreate(func_id, env_heap, closure_ty);
|
||||
} else {
|
||||
@@ -6369,31 +6539,26 @@ pub const Lowering = struct {
|
||||
|
||||
// ── Generic monomorphization ──────────────────────────────────
|
||||
|
||||
/// Lower a call to a generic function by monomorphizing it with inferred type arguments.
|
||||
fn lowerGenericCall(self: *Lowering, fd: *const ast.FnDecl, base_name: []const u8, call_node: *const ast.Call, lowered_args: []Ref) Ref {
|
||||
// Infer type param bindings from call arguments
|
||||
/// Build `tp.name -> TypeId` bindings for a generic call.
|
||||
/// `args_ast` must be parallel to `fd.params`; for dot-calls the caller
|
||||
/// prepends the receiver's AST node so positions align with `fd.params[0] = self`.
|
||||
/// Caller owns the returned map and must call `.deinit()`.
|
||||
fn buildTypeBindings(
|
||||
self: *Lowering,
|
||||
fd: *const ast.FnDecl,
|
||||
args_ast: []const *const Node,
|
||||
) std.StringHashMap(TypeId) {
|
||||
var bindings = std.StringHashMap(TypeId).init(self.alloc);
|
||||
defer bindings.deinit();
|
||||
|
||||
// Determine if type args are passed explicitly:
|
||||
// If call_node.args.len == fd.params.len, the caller passed type args explicitly
|
||||
// (e.g., are_equal(Point, p1, p2)). Otherwise, types are inferred from value args
|
||||
// (e.g., are_equal(p1, p2)).
|
||||
const types_passed_explicitly = call_node.args.len == fd.params.len;
|
||||
|
||||
const types_passed_explicitly = args_ast.len == fd.params.len;
|
||||
for (fd.type_params) |tp| {
|
||||
var found = false;
|
||||
|
||||
// Strategy 1: Direct type param declaration ($T: Type)
|
||||
// The param whose name matches the type param IS the declaration.
|
||||
// The call arg at that position is a type expression — resolve it directly.
|
||||
// Only applies when type args are passed explicitly in the call.
|
||||
// Strategy 1: explicit — the param whose name matches `tp.name` IS
|
||||
// the `$T: Type` declaration; the arg at that position is a type expression.
|
||||
if (types_passed_explicitly) {
|
||||
for (fd.params, 0..) |param, pi| {
|
||||
if (std.mem.eql(u8, param.name, tp.name)) {
|
||||
// This param IS the type param declaration
|
||||
if (pi < call_node.args.len) {
|
||||
const ty = self.resolveTypeArg(call_node.args[pi]);
|
||||
if (pi < args_ast.len and type_bridge.isTypeShapedAstNode(args_ast[pi], &self.module.types)) {
|
||||
const ty = self.resolveTypeArg(args_ast[pi]);
|
||||
bindings.put(tp.name, ty) catch {};
|
||||
found = true;
|
||||
}
|
||||
@@ -6402,11 +6567,8 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
if (found) continue;
|
||||
|
||||
// Strategy 2: Infer from params that USE the type param (e.g., a: $T, b: T, items: []$T)
|
||||
// Check ALL params whose type matches the type param name, pick widest type.
|
||||
// When types are inferred (not explicit), use a separate arg index that
|
||||
// skips type param declarations to correctly map params to call args.
|
||||
// Strategy 2: infer from value params that USE the type param
|
||||
// (e.g. a: $T, b: T, items: []$T). Pick widest type across matches.
|
||||
var inferred_ty: ?TypeId = null;
|
||||
var s2_arg_idx: usize = 0;
|
||||
for (fd.params) |param| {
|
||||
@@ -6420,8 +6582,8 @@ pub const Lowering = struct {
|
||||
}
|
||||
const matched = self.matchTypeParam(param.type_expr, tp.name);
|
||||
if (matched) {
|
||||
if (s2_arg_idx < call_node.args.len) {
|
||||
const arg_ty = self.inferExprType(call_node.args[s2_arg_idx]);
|
||||
if (s2_arg_idx < args_ast.len) {
|
||||
const arg_ty = self.inferExprType(args_ast[s2_arg_idx]);
|
||||
const extracted = self.extractTypeParam(param.type_expr, arg_ty, tp.name);
|
||||
if (extracted) |ety| {
|
||||
if (inferred_ty) |prev| {
|
||||
@@ -6441,8 +6603,17 @@ pub const Lowering = struct {
|
||||
bindings.put(tp.name, ty) catch {};
|
||||
}
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
// Build mangled name: "func_name__Type1_Type2"
|
||||
/// Mangle a generic call site into "base__Type1_Type2".
|
||||
/// Returns a heap-allocated string owned by self.alloc.
|
||||
fn mangleGenericName(
|
||||
self: *Lowering,
|
||||
base_name: []const u8,
|
||||
fd: *const ast.FnDecl,
|
||||
bindings: *const std.StringHashMap(TypeId),
|
||||
) []const u8 {
|
||||
var mangled_buf: [256]u8 = undefined;
|
||||
var mangled_len: usize = 0;
|
||||
for (base_name) |ch| {
|
||||
@@ -6452,14 +6623,12 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
for (fd.type_params) |tp| {
|
||||
// Append separator
|
||||
for ("__") |ch| {
|
||||
if (mangled_len < mangled_buf.len) {
|
||||
mangled_buf[mangled_len] = ch;
|
||||
mangled_len += 1;
|
||||
}
|
||||
}
|
||||
// Append type name
|
||||
const ty = bindings.get(tp.name) orelse .s64;
|
||||
const type_name_str = self.mangleTypeName(ty);
|
||||
for (type_name_str) |ch| {
|
||||
@@ -6469,27 +6638,31 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
const mangled_name = mangled_buf[0..mangled_len];
|
||||
return self.alloc.dupe(u8, mangled_buf[0..mangled_len]) catch base_name;
|
||||
}
|
||||
|
||||
/// Lower a call to a generic function by monomorphizing it with inferred type arguments.
|
||||
fn lowerGenericCall(self: *Lowering, fd: *const ast.FnDecl, base_name: []const u8, call_node: *const ast.Call, lowered_args: []Ref) Ref {
|
||||
var bindings = self.buildTypeBindings(fd, call_node.args);
|
||||
defer bindings.deinit();
|
||||
|
||||
const types_passed_explicitly = call_node.args.len == fd.params.len;
|
||||
const mangled_name = self.mangleGenericName(base_name, fd, &bindings);
|
||||
|
||||
// Check cache
|
||||
if (!self.lowered_functions.contains(mangled_name)) {
|
||||
// Monomorphize: create a new function with the mangled name and lower with type bindings
|
||||
self.monomorphizeFunction(fd, mangled_name, &bindings);
|
||||
}
|
||||
|
||||
// Resolve the monomorphized function and call it (stripping type args)
|
||||
if (self.resolveFuncByName(mangled_name)) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
// Build value-only args (skip type param declaration args)
|
||||
// Use separate index for lowered_args since type params don't consume call args
|
||||
var value_args = std.ArrayList(Ref).empty;
|
||||
defer value_args.deinit(self.alloc);
|
||||
var arg_idx: usize = 0;
|
||||
for (fd.params) |p| {
|
||||
if (isTypeParamDecl(&p, fd.type_params)) {
|
||||
// Only skip in lowered_args if types were passed explicitly in the call
|
||||
if (types_passed_explicitly) arg_idx += 1;
|
||||
continue;
|
||||
}
|
||||
@@ -6894,6 +7067,11 @@ pub const Lowering = struct {
|
||||
const size: i64 = @intCast(self.typeSizeBytes(ty));
|
||||
return self.builder.constInt(size, .s64);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "align_of")) {
|
||||
const ty = self.resolveTypeArg(c.args[0]);
|
||||
const a: i64 = @intCast(self.module.types.typeAlignBytes(ty));
|
||||
return self.builder.constInt(a, .s64);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "field_count")) {
|
||||
// field_count(T) → const_int(N)
|
||||
const ty = self.resolveTypeArg(c.args[0]);
|
||||
@@ -7019,9 +7197,13 @@ pub const Lowering = struct {
|
||||
if (self.type_bindings) |tb| {
|
||||
if (tb.get(id.name)) |ty| return ty;
|
||||
}
|
||||
// Try as a named type by name (resolveAstType doesn't handle .identifier)
|
||||
if (self.type_alias_map.get(id.name)) |alias_ty| return alias_ty;
|
||||
const name_id = self.module.types.internString(id.name);
|
||||
return self.module.types.findByName(name_id) orelse .s64;
|
||||
if (self.module.types.findByName(name_id)) |t| return t;
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "unresolved type: '{s}'", .{id.name});
|
||||
}
|
||||
return .void;
|
||||
},
|
||||
.type_expr => |te| {
|
||||
if (self.type_alias_map.get(te.name)) |alias_ty| return alias_ty;
|
||||
@@ -7031,6 +7213,14 @@ pub const Lowering = struct {
|
||||
// Handle type constructor calls: size_of(Sx(f32)), size_of(Complex(u32))
|
||||
return self.resolveTypeCallWithBindings(&cl);
|
||||
},
|
||||
.pointer_type_expr,
|
||||
.many_pointer_type_expr,
|
||||
.array_type_expr,
|
||||
.slice_type_expr,
|
||||
.optional_type_expr,
|
||||
.function_type_expr,
|
||||
.tuple_literal,
|
||||
=> return type_bridge.resolveAstType(node, &self.module.types),
|
||||
else => return .s64,
|
||||
}
|
||||
}
|
||||
@@ -7498,6 +7688,41 @@ pub const Lowering = struct {
|
||||
// skipping the first param (self) since it's prepended later.
|
||||
if (c.callee.data == .field_access) {
|
||||
const fa = c.callee.data.field_access;
|
||||
|
||||
// Namespace/static call: `Type.method(args)` where `Type` is a type
|
||||
// identifier (not a value in scope). Args correspond to ALL params
|
||||
// — no self prepend — so target_type for arg lowering must include
|
||||
// the leading param. Skipping it would lose the protocol context
|
||||
// for `xx ptr` inline-cast args.
|
||||
if (fa.object.data == .identifier) {
|
||||
const obj_name = fa.object.data.identifier.name;
|
||||
const is_value = blk: {
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(obj_name) != null) break :blk true;
|
||||
}
|
||||
if (self.global_names.contains(obj_name)) break :blk true;
|
||||
break :blk false;
|
||||
};
|
||||
if (!is_value) {
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ obj_name, fa.field }) catch return &.{};
|
||||
if (self.resolveFuncByName(qualified)) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
var types_list = std.ArrayList(TypeId).empty;
|
||||
for (func.params) |p| {
|
||||
types_list.append(self.alloc, p.ty) catch unreachable;
|
||||
}
|
||||
return types_list.items;
|
||||
}
|
||||
if (self.fn_ast_map.get(qualified)) |fd| {
|
||||
var types_list = std.ArrayList(TypeId).empty;
|
||||
for (fd.params) |p| {
|
||||
types_list.append(self.alloc, self.resolveParamType(&p)) catch unreachable;
|
||||
}
|
||||
return types_list.items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const obj_ty = self.inferExprType(fa.object);
|
||||
// Protocol-typed receiver: look up the method on the protocol decl. The
|
||||
// protocol's ProtocolMethodInfo.param_types already excludes self.
|
||||
@@ -8511,9 +8736,11 @@ pub const Lowering = struct {
|
||||
};
|
||||
ptypes.append(self.alloc, pty) catch unreachable;
|
||||
}
|
||||
var ret_is_self = false;
|
||||
const ret = if (method.return_type) |rt| blk: {
|
||||
if (rt.data == .type_expr) {
|
||||
if (std.mem.eql(u8, rt.data.type_expr.name, "Self")) {
|
||||
ret_is_self = true;
|
||||
break :blk void_ptr_ty;
|
||||
}
|
||||
if (self.type_alias_map.get(rt.data.type_expr.name)) |aliased| {
|
||||
@@ -8526,6 +8753,7 @@ pub const Lowering = struct {
|
||||
.name = method.name,
|
||||
.param_types = self.alloc.dupe(TypeId, ptypes.items) catch unreachable,
|
||||
.ret_type = ret,
|
||||
.ret_is_self = ret_is_self,
|
||||
}) catch unreachable;
|
||||
}
|
||||
self.protocol_decl_map.put(pd.name, .{
|
||||
@@ -8799,6 +9027,54 @@ pub const Lowering = struct {
|
||||
return owned;
|
||||
}
|
||||
|
||||
/// Emit the process-wide default Context as an LLVM static constant.
|
||||
///
|
||||
/// @__sx_default_context = internal constant %Context {
|
||||
/// %Allocator { ptr null,
|
||||
/// ptr @__thunk_CAllocator_Allocator_alloc,
|
||||
/// ptr @__thunk_CAllocator_Allocator_dealloc },
|
||||
/// ptr null
|
||||
/// }
|
||||
///
|
||||
/// Used by FFI inbound wrappers (Step 4) and the interp's default-
|
||||
/// context call entry (Step 7). Only emitted when the program imports
|
||||
/// `std.sx` — without that, Context / Allocator / CAllocator aren't
|
||||
/// registered and the global has no purpose.
|
||||
fn emitDefaultContextGlobal(self: *Lowering) void {
|
||||
const tbl = &self.module.types;
|
||||
const ctx_name_id = tbl.internString("Context");
|
||||
const ctx_ty = tbl.findByName(ctx_name_id) orelse return;
|
||||
if (tbl.findByName(tbl.internString("Allocator")) == null) return;
|
||||
if (tbl.findByName(tbl.internString("CAllocator")) == null) return;
|
||||
|
||||
// Force the CAllocator → Allocator thunks to exist so we can
|
||||
// reference them by FuncId in the static initializer.
|
||||
const thunks = self.getOrCreateThunks("Allocator", "CAllocator");
|
||||
if (thunks.len < 2) return;
|
||||
|
||||
// Inline Allocator value: { ctx: *void, alloc_fn: *void, dealloc_fn: *void }
|
||||
// CAllocator is stateless, so ctx is null.
|
||||
const alloc_fields = self.alloc.alloc(inst_mod.ConstantValue, 3) catch return;
|
||||
alloc_fields[0] = .null_val;
|
||||
alloc_fields[1] = .{ .func_ref = thunks[0] };
|
||||
alloc_fields[2] = .{ .func_ref = thunks[1] };
|
||||
|
||||
// Context value: { allocator: Allocator, data: *void }
|
||||
const ctx_fields = self.alloc.alloc(inst_mod.ConstantValue, 2) catch return;
|
||||
ctx_fields[0] = .{ .aggregate = alloc_fields };
|
||||
ctx_fields[1] = .null_val;
|
||||
|
||||
const global_name = "__sx_default_context";
|
||||
const global_name_id = tbl.internString(global_name);
|
||||
const gid = self.module.addGlobal(.{
|
||||
.name = global_name_id,
|
||||
.ty = ctx_ty,
|
||||
.init_val = .{ .aggregate = ctx_fields },
|
||||
.is_const = true,
|
||||
});
|
||||
self.global_names.put(global_name, .{ .id = gid, .ty = ctx_ty }) catch {};
|
||||
}
|
||||
|
||||
/// Create a thunk function: __thunk_ConcreteType_Protocol_method(ctx: *void, args...) -> ret
|
||||
/// The thunk calls ConcreteType.method(ctx, args...).
|
||||
fn createProtocolThunk(self: *Lowering, proto_name: []const u8, concrete_type_name: []const u8, method: ProtocolMethodInfo) FuncId {
|
||||
@@ -8925,12 +9201,8 @@ pub const Lowering = struct {
|
||||
if (heap_copy) {
|
||||
const concrete_size = self.module.types.typeSizeBytes(concrete_ty);
|
||||
const size_ref = self.builder.constInt(@intCast(concrete_size), .s64);
|
||||
const heap_ptr = self.builder.emit(.{ .heap_alloc = .{ .operand = size_ref } }, void_ptr_ty);
|
||||
const memcpy_args = self.alloc.dupe(Ref, &.{ heap_ptr, concrete_ptr, size_ref }) catch unreachable;
|
||||
_ = self.builder.emit(.{ .call_builtin = .{
|
||||
.builtin = inst_mod.BuiltinId.memcpy,
|
||||
.args = memcpy_args,
|
||||
} }, void_ptr_ty);
|
||||
const heap_ptr = self.allocViaContext(size_ref, void_ptr_ty);
|
||||
_ = self.callForeign("memcpy", &.{ heap_ptr, concrete_ptr, size_ref }, void_ptr_ty);
|
||||
ctx_ptr = heap_ptr;
|
||||
}
|
||||
|
||||
@@ -9055,12 +9327,11 @@ pub const Lowering = struct {
|
||||
const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
||||
const raw_result = self.builder.emit(.{ .call_indirect = .{ .callee = fn_ptr, .args = owned } }, mi.ret_type);
|
||||
|
||||
// If protocol method returns *void (Self) and the caller expects a value type,
|
||||
// unbox: load the concrete value from the returned pointer. Real pointer
|
||||
// returns (declared `-> *T` for non-Self T) are NOT auto-loaded — the
|
||||
// pointee may be a single byte and reading `sizeof(target)` past it
|
||||
// segfaults. Self is encoded as `*void`, so test against that exact type.
|
||||
if (mi.ret_type == void_ptr) {
|
||||
// If the protocol method was declared `-> Self` (encoded here as *void)
|
||||
// and the caller expects a value type, unbox: load the concrete value
|
||||
// from the returned pointer. A literal `-> *void` return is NOT
|
||||
// auto-loaded — it's a real pointer whose pointee size we don't know.
|
||||
if (mi.ret_is_self) {
|
||||
if (self.target_type) |target| {
|
||||
const target_info = self.module.types.get(target);
|
||||
if (target_info != .pointer) {
|
||||
@@ -9149,7 +9420,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
break :blk TypeId.f64;
|
||||
},
|
||||
.size_of, .malloc => .s64,
|
||||
.size_of, .align_of => .s64,
|
||||
.cast => if (c.args.len > 0) self.resolveTypeArg(c.args[0]) else .s64,
|
||||
else => .s64,
|
||||
};
|
||||
@@ -9376,15 +9647,17 @@ pub const Lowering = struct {
|
||||
defer tmp_bindings.deinit();
|
||||
|
||||
for (fd.type_params) |tp| {
|
||||
// Strategy 1: direct type param decl ($T: Type) — param.name == tp.name
|
||||
// Strategy 1: direct type param decl ($T: Type) — param.name == tp.name.
|
||||
// Only fires when the caller actually supplied a type expression at
|
||||
// that position; otherwise fall through to value-based inference.
|
||||
var found = false;
|
||||
for (fd.params, 0..) |param, pi| {
|
||||
if (std.mem.eql(u8, param.name, tp.name)) {
|
||||
if (pi < c.args.len) {
|
||||
if (pi < c.args.len and type_bridge.isTypeShapedAstNode(c.args[pi], &self.module.types)) {
|
||||
const ty = self.resolveTypeArg(c.args[pi]);
|
||||
tmp_bindings.put(tp.name, ty) catch {};
|
||||
found = true;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -9482,6 +9755,21 @@ pub const Lowering = struct {
|
||||
return self.buildProtocolErasure(operand, operand_node, src_ty, dst_ty);
|
||||
}
|
||||
|
||||
// Protocol → pointer: recover the typed ctx pointer (field 0).
|
||||
// The protocol value is `{ ctx, fn1, fn2, ... }` (inline) or
|
||||
// `{ ctx, vtable_ptr }` — either way, ctx lives at field 0.
|
||||
if (self.getProtocolInfo(src_ty)) |_| {
|
||||
if (!dst_ty.isBuiltin()) {
|
||||
const dst_info = self.module.types.get(dst_ty);
|
||||
if (dst_info == .pointer) {
|
||||
const void_ptr_ty = self.module.types.ptrTo(.void);
|
||||
const ctx_ref = self.builder.emit(.{ .struct_get = .{ .base = operand, .field_index = 0 } }, void_ptr_ty);
|
||||
if (dst_ty == void_ptr_ty) return ctx_ref;
|
||||
return self.builder.emit(.{ .bitcast = .{ .operand = ctx_ref, .from = void_ptr_ty, .to = dst_ty } }, dst_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = self.coerceToType(operand, src_ty, dst_ty);
|
||||
|
||||
// User-space fallback via `impl Into(Target) for Source`. Only fires
|
||||
@@ -9900,7 +10188,26 @@ pub const Lowering = struct {
|
||||
|
||||
fn emitError(self: *Lowering, name: []const u8, span: ?ast.Span) Ref {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, span, "unresolved: '{s}'", .{name});
|
||||
// The literal message carries the lowering's `current_source_file`
|
||||
// and enclosing function name. The diagnostic renderer's
|
||||
// `source_file` -> `file:line:col` prefix can drift when a span is
|
||||
// offset into one source but the diagnostic falls back to another
|
||||
// (e.g. synthetic AST nodes inserted from `#insert` take their
|
||||
// span from the call site, not from the string being inserted).
|
||||
// Embedding the file + function in the message means a
|
||||
// misattributed span can never hide WHERE the lookup actually
|
||||
// failed. Setting SX_TRACE_UNRESOLVED=1 also dumps a Zig stack
|
||||
// trace at the emit site to surface the calling lowering path.
|
||||
const sf = self.current_source_file orelse "<unknown>";
|
||||
const fn_name: []const u8 = if (self.builder.func) |fid|
|
||||
self.module.types.getString(self.module.functions.items[@intFromEnum(fid)].name)
|
||||
else
|
||||
"<top-level>";
|
||||
if (std.c.getenv("SX_TRACE_UNRESOLVED") != null) {
|
||||
std.debug.print("\n== unresolved '{s}' (in {s} fn {s}) ==\n", .{ name, sf, fn_name });
|
||||
std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() });
|
||||
}
|
||||
diags.addFmt(.err, span, "unresolved '{s}' (in {s} fn {s})", .{ name, sf, fn_name });
|
||||
}
|
||||
return self.emitPlaceholder(name);
|
||||
}
|
||||
|
||||
@@ -516,6 +516,7 @@ fn writeConstant(val: ConstantValue, writer: Writer) !void {
|
||||
.zeroinit => try writer.writeAll("zeroinit"),
|
||||
.aggregate => try writer.writeAll("{...}"),
|
||||
.vtable => try writer.writeAll("vtable{...}"),
|
||||
.func_ref => |fid| try writer.print("func_ref(#{d})", .{fid.index()}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable) TypeId {
|
||||
.function_type_expr => |ft| resolveFunctionType(&ft, table),
|
||||
.closure_type_expr => |ct| resolveClosureType(&ct, table),
|
||||
.tuple_type_expr => |tt| resolveTupleType(&tt, table),
|
||||
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table),
|
||||
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table),
|
||||
.inferred_type => .s64, // inferred — default until we have type inference
|
||||
// Inline type declarations (used as field types)
|
||||
@@ -299,6 +300,62 @@ fn resolveTupleType(tt: *const ast.TupleTypeExpr, table: *TypeTable) TypeId {
|
||||
} });
|
||||
}
|
||||
|
||||
// Treat a tuple value literal as the corresponding tuple TYPE — valid only when
|
||||
// every element is itself a type expression. Non-type elements report a clear
|
||||
// diagnostic and degrade to .s64 for that slot (which the snapshot will catch).
|
||||
fn resolveTupleLiteralAsType(tl: *const ast.TupleLiteral, table: *TypeTable) TypeId {
|
||||
const alloc = table.alloc;
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
var name_ids_list = std.ArrayList(StringId).empty;
|
||||
var any_named = false;
|
||||
for (tl.elements) |el| {
|
||||
if (!isTypeShapedAstNode(el.value, table)) {
|
||||
std.debug.print("type_bridge: tuple literal element is not a type (tag={s}) — cannot use as tuple type\n", .{@tagName(el.value.data)});
|
||||
field_ids.append(alloc, .s64) catch unreachable;
|
||||
} else {
|
||||
field_ids.append(alloc, resolveAstType(el.value, table)) catch unreachable;
|
||||
}
|
||||
if (el.name) |n| {
|
||||
any_named = true;
|
||||
name_ids_list.append(alloc, table.internString(n)) catch unreachable;
|
||||
} else {
|
||||
name_ids_list.append(alloc, table.internString("")) catch unreachable;
|
||||
}
|
||||
}
|
||||
const names: ?[]const StringId = if (any_named) name_ids_list.items else null;
|
||||
return table.intern(.{ .tuple = .{
|
||||
.fields = field_ids.items,
|
||||
.names = names,
|
||||
} });
|
||||
}
|
||||
|
||||
// Returns true when this AST node, on its own, denotes a type rather than a
|
||||
// value. Used to guard tuple-literal-as-type reinterpretation: a tuple literal
|
||||
// becomes a tuple type only when every element is a type.
|
||||
pub fn isTypeShapedAstNode(node: *const Node, table: *TypeTable) bool {
|
||||
return switch (node.data) {
|
||||
.type_expr,
|
||||
.pointer_type_expr,
|
||||
.many_pointer_type_expr,
|
||||
.array_type_expr,
|
||||
.slice_type_expr,
|
||||
.optional_type_expr,
|
||||
.function_type_expr,
|
||||
.closure_type_expr,
|
||||
.tuple_type_expr,
|
||||
.parameterized_type_expr,
|
||||
=> true,
|
||||
.identifier => |id| table.findByName(table.internString(id.name)) != null,
|
||||
.tuple_literal => |tl| blk: {
|
||||
for (tl.elements) |el| {
|
||||
if (!isTypeShapedAstNode(el.value, table)) break :blk false;
|
||||
}
|
||||
break :blk true;
|
||||
},
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTable) TypeId {
|
||||
// Strip module prefix (e.g. "std.Vector" → "Vector")
|
||||
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
||||
|
||||
@@ -505,7 +505,8 @@ pub const Server = struct {
|
||||
.{ .label = "field_count", .detail = "($T: Type) -> s32" },
|
||||
.{ .label = "field_name", .detail = "($T: Type, idx: s32) -> string" },
|
||||
.{ .label = "field_value", .detail = "(s: $T, idx: s32) -> Any" },
|
||||
.{ .label = "size_of", .detail = "($T: Type) -> s32" },
|
||||
.{ .label = "size_of", .detail = "($T: Type) -> s64" },
|
||||
.{ .label = "align_of", .detail = "($T: Type) -> s64" },
|
||||
.{ .label = "cast", .detail = "(Type) expr — prefix type cast" },
|
||||
.{ .label = "malloc", .detail = "(size: s64) -> *void" },
|
||||
.{ .label = "free", .detail = "(ptr: *void) -> void" },
|
||||
@@ -974,7 +975,8 @@ pub const Server = struct {
|
||||
.{ .name = "field_count", .label = "field_count($T: Type) -> s32", .params = &.{"$T: Type"} },
|
||||
.{ .name = "field_name", .label = "field_name($T: Type, idx: s32) -> string", .params = &.{ "$T: Type", "idx: s32" } },
|
||||
.{ .name = "field_value", .label = "field_value(s: $T, idx: s32) -> Any", .params = &.{ "s: $T", "idx: s32" } },
|
||||
.{ .name = "size_of", .label = "size_of($T: Type) -> s32", .params = &.{"$T: Type"} },
|
||||
.{ .name = "size_of", .label = "size_of($T: Type) -> s64", .params = &.{"$T: Type"} },
|
||||
.{ .name = "align_of", .label = "align_of($T: Type) -> s64", .params = &.{"$T: Type"} },
|
||||
.{ .name = "cast", .label = "cast(Type) expr", .params = &.{"Type"} },
|
||||
.{ .name = "malloc", .label = "malloc(size: s64) -> *void", .params = &.{"size: s64"} },
|
||||
.{ .name = "free", .label = "free(ptr: *void) -> void", .params = &.{"ptr: *void"} },
|
||||
|
||||
@@ -2240,6 +2240,10 @@ pub const Parser = struct {
|
||||
if (self.isLambda()) {
|
||||
return self.parseLambda();
|
||||
}
|
||||
// Function-type literal: (T1, T2) -> R (no body — isLambda would have caught a body)
|
||||
if (self.isFunctionTypeExprAtLParen()) {
|
||||
return try self.parseTypeExpr();
|
||||
}
|
||||
self.advance(); // skip '('
|
||||
|
||||
// Check for named tuple: (name: expr, ...)
|
||||
@@ -2312,8 +2316,7 @@ pub const Parser = struct {
|
||||
null;
|
||||
return try self.createNode(start, .{ .return_stmt = .{ .value = value } });
|
||||
},
|
||||
.l_bracket => {
|
||||
// Type expression in expression position: []T.[...] or [N]T.[...]
|
||||
.l_bracket, .star, .question => {
|
||||
return try self.parseTypeExpr();
|
||||
},
|
||||
.l_brace => {
|
||||
@@ -2728,6 +2731,32 @@ pub const Parser = struct {
|
||||
return self.current.tag;
|
||||
}
|
||||
|
||||
/// Returns true when the current `(` opens a function-type literal `(T1, T2) -> R`
|
||||
/// rather than a tuple/grouping/lambda. Only meaningful after `isLambda` has
|
||||
/// returned false — at that point a trailing `->` after the matching `)` can
|
||||
/// only be a function type, since any body (`=>` or `{`) would have made it
|
||||
/// a lambda.
|
||||
fn isFunctionTypeExprAtLParen(self: *Parser) bool {
|
||||
const saved_lexer = self.lexer;
|
||||
const saved_current = self.current;
|
||||
const saved_prev_end = self.prev_end;
|
||||
defer {
|
||||
self.lexer = saved_lexer;
|
||||
self.current = saved_current;
|
||||
self.prev_end = saved_prev_end;
|
||||
}
|
||||
self.advance(); // skip '('
|
||||
var depth: u32 = 1;
|
||||
while (depth > 0 and self.current.tag != .eof) {
|
||||
if (self.current.tag == .l_paren) depth += 1;
|
||||
if (self.current.tag == .r_paren) depth -= 1;
|
||||
if (depth > 0) self.advance();
|
||||
}
|
||||
if (self.current.tag != .r_paren) return false;
|
||||
self.advance(); // skip ')'
|
||||
return self.current.tag == .arrow;
|
||||
}
|
||||
|
||||
fn isLambda(self: *Parser) bool {
|
||||
const saved_lexer = self.lexer;
|
||||
const saved_current = self.current;
|
||||
@@ -2845,7 +2874,55 @@ pub const Parser = struct {
|
||||
// ends with `;` directly after the param list — recognise it as a
|
||||
// function def (not a constant) so it goes through parseFnDecl.
|
||||
if (self.struct_default_compiler and tag == .semicolon) return true;
|
||||
return tag == .l_brace or tag == .arrow or tag == .hash_builtin or tag == .hash_compiler or tag == .hash_foreign or tag == .fat_arrow or tag == .kw_callconv;
|
||||
// `(T1, T2) -> R` without a trailing body (`{`, `=>`, or a foreign/
|
||||
// builtin marker) is a function-type literal, not a function def.
|
||||
if (tag == .arrow) return self.hasFnBodyAfterArrow();
|
||||
return tag == .l_brace or tag == .hash_builtin or tag == .hash_compiler or tag == .hash_foreign or tag == .fat_arrow or tag == .kw_callconv;
|
||||
}
|
||||
|
||||
fn hasFnBodyAfterArrow(self: *Parser) bool {
|
||||
const saved_lexer = self.lexer;
|
||||
const saved_current = self.current;
|
||||
const saved_prev_end = self.prev_end;
|
||||
defer {
|
||||
self.lexer = saved_lexer;
|
||||
self.current = saved_current;
|
||||
self.prev_end = saved_prev_end;
|
||||
}
|
||||
self.advance(); // skip '('
|
||||
var depth: u32 = 1;
|
||||
while (depth > 0 and self.current.tag != .eof) {
|
||||
if (self.current.tag == .l_paren) depth += 1;
|
||||
if (self.current.tag == .r_paren) depth -= 1;
|
||||
if (depth > 0) self.advance();
|
||||
}
|
||||
if (self.current.tag != .r_paren) return false;
|
||||
self.advance(); // skip ')'
|
||||
if (self.current.tag != .arrow) return false;
|
||||
self.advance(); // skip '->'
|
||||
while (self.current.tag != .eof) {
|
||||
if (self.current.tag == .fat_arrow) return true;
|
||||
if (self.current.tag == .l_brace) return true;
|
||||
if (self.current.tag == .hash_builtin or self.current.tag == .hash_compiler or self.current.tag == .hash_foreign) return true;
|
||||
if (self.current.tag == .kw_callconv) return true;
|
||||
// Inside a `struct #compiler` block, a `(...) -> Ret;` ending
|
||||
// with `;` after the return type is a `#compiler` method
|
||||
// declaration (body implicit). Outside that context, the same
|
||||
// shape is a function-type alias (no body) and falls through to
|
||||
// const-decl parsing.
|
||||
if (self.struct_default_compiler and self.current.tag == .semicolon) return true;
|
||||
if (self.current.tag == .identifier or self.current.tag.isTypeKeyword() or
|
||||
self.current.tag == .dot or self.current.tag == .dollar or
|
||||
self.current.tag == .l_bracket or self.current.tag == .r_bracket or
|
||||
self.current.tag == .l_paren or self.current.tag == .r_paren or
|
||||
self.current.tag == .comma or self.current.tag == .int_literal or
|
||||
self.current.tag == .star or self.current.tag == .question or
|
||||
self.current.tag == .colon or self.current.tag == .arrow)
|
||||
{
|
||||
self.advance();
|
||||
} else break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn parseOptionalCallConv(self: *Parser) anyerror!ast.CallingConvention {
|
||||
|
||||
@@ -655,7 +655,7 @@ pub const Analyzer = struct {
|
||||
}
|
||||
|
||||
// Built-in names that aren't declared in source
|
||||
const builtins = [_][]const u8{ "io", "true", "false", "cast", "closure", "out", "size_of", "malloc", "free", "memcpy", "memset" };
|
||||
const builtins = [_][]const u8{ "io", "true", "false", "cast", "closure", "out", "size_of", "align_of", "malloc", "free", "memcpy", "memset" };
|
||||
for (builtins) |b| {
|
||||
if (std.mem.eql(u8, name, b)) return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
a 0 : Foo{a: 0, b: 42, c: 176, d: 17}
|
||||
a 0 : Foo{a: 0, b: 42, c: 92, d: 17}
|
||||
a 1 : Foo{a: 1, b: 42, c: 8, d: 17}
|
||||
b: Foo{a: 1, b: 1, c: 101, d: 1}
|
||||
Pack{a: 1, b: 0, c: 3, d: 5, f: 9, v: 100, x: 3.500000}
|
||||
|
||||
@@ -12,5 +12,3 @@ sqrt(9): 3.000000
|
||||
4
|
||||
16
|
||||
8
|
||||
8
|
||||
8
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,3 @@
|
||||
Box.init (named-var): ok
|
||||
make_box (inline-xx): ok
|
||||
Box.init (inline-xx): ok
|
||||
1
tests/expected/126-xx-recover-then-dispatch.exit
Normal file
1
tests/expected/126-xx-recover-then-dispatch.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
4
tests/expected/126-xx-recover-then-dispatch.txt
Normal file
4
tests/expected/126-xx-recover-then-dispatch.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
recovered == gpa? true
|
||||
alloc count after first alloc: 1
|
||||
recovered2 == gpa? true
|
||||
alloc count after dealloc: 0
|
||||
1
tests/expected/127-import-non-transitive.exit
Normal file
1
tests/expected/127-import-non-transitive.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
3
tests/expected/127-import-non-transitive.txt
Normal file
3
tests/expected/127-import-non-transitive.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
/Users/agra/projects/sx/examples/127-import-non-transitive.sx:15:37: error: 'c_only_fn' is not visible; #import the module that declares it
|
||||
/Users/agra/projects/sx/examples/127-import-non-transitive.sx:16:40: error: 'c_only_const' is not visible; #import the module that declares it
|
||||
/Users/agra/projects/sx/examples/127-import-non-transitive.sx:16:40: error: unresolved 'c_only_const' (in /Users/agra/projects/sx/examples/127-import-non-transitive.sx fn main)
|
||||
1
tests/expected/128-import-dir-scan-order.exit
Normal file
1
tests/expected/128-import-dir-scan-order.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
tests/expected/128-import-dir-scan-order.txt
Normal file
1
tests/expected/128-import-dir-scan-order.txt
Normal file
@@ -0,0 +1 @@
|
||||
b=42
|
||||
1
tests/expected/129-generic-method-dot-call.exit
Normal file
1
tests/expected/129-generic-method-dot-call.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
5
tests/expected/129-generic-method-dot-call.txt
Normal file
5
tests/expected/129-generic-method-dot-call.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
plain: 7
|
||||
sized s32: 4
|
||||
sized s64: 8
|
||||
taking explicit: 42
|
||||
taking inferred: 99
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
Tracer.count = 1
|
||||
@@ -228,6 +228,10 @@ sqrt-f64: 4.000000
|
||||
sizeof-s32: 4
|
||||
sizeof-f64: 8
|
||||
sizeof-struct: 8
|
||||
alignof-u8: 1
|
||||
alignof-s32: 4
|
||||
alignof-s64: 8
|
||||
alignof-struct: 4
|
||||
typeof: int
|
||||
typeof-float: float
|
||||
typeof-string: string
|
||||
@@ -385,12 +389,12 @@ bytes len: 3
|
||||
--- allocators ---
|
||||
gpa allocs: 2
|
||||
gpa final: 0
|
||||
arena chunks: 1
|
||||
arena overflow: 2
|
||||
arena chunks: 2
|
||||
arena overflow: 3
|
||||
arena a1: 42
|
||||
arena a3: 99
|
||||
arena reset idx: 0
|
||||
arena reset gpa: 1
|
||||
arena reset gpa: 2
|
||||
arena deinit: 0
|
||||
buf pos: 48
|
||||
buf overflow: 0
|
||||
|
||||
1
tests/expected/99-protocol-void-pointer-return.exit
Normal file
1
tests/expected/99-protocol-void-pointer-return.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
3
tests/expected/99-protocol-void-pointer-return.txt
Normal file
3
tests/expected/99-protocol-void-pointer-return.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
direct: null? false
|
||||
protocol: null? false
|
||||
alloc_count: 2
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [5 x i8] c"noop\00", align 1
|
||||
@str.1 = private unnamed_addr constant [4 x i8] c"()V\00", align 1
|
||||
@SX_JNI_CLS_noop____V = internal global ptr null
|
||||
@@ -15,14 +16,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -37,8 +62,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -62,7 +87,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -77,7 +102,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -88,6 +113,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -156,7 +196,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -164,7 +204,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -189,7 +229,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -310,6 +350,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [9 x i8] c"getCount\00", align 1
|
||||
@str.1 = private unnamed_addr constant [4 x i8] c"()I\00", align 1
|
||||
@SX_JNI_CLS_getCount____I = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [18 x i8] c"currentTimeMillis\00", align 1
|
||||
@str.1 = private unnamed_addr constant [4 x i8] c"()J\00", align 1
|
||||
@SX_JNI_CLS_currentTimeMillis____J = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [9 x i8] c"getValue\00", align 1
|
||||
@str.1 = private unnamed_addr constant [4 x i8] c"()D\00", align 1
|
||||
@SX_JNI_CLS_getValue____D = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [8 x i8] c"isShown\00", align 1
|
||||
@str.1 = private unnamed_addr constant [4 x i8] c"()Z\00", align 1
|
||||
@SX_JNI_CLS_isShown____Z = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [10 x i8] c"getWindow\00", align 1
|
||||
@str.1 = private unnamed_addr constant [24 x i8] c"()Landroid/view/Window;\00", align 1
|
||||
@SX_JNI_CLS_getWindow____Landroid_view_Window_ = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [4 x i8] c"max\00", align 1
|
||||
@str.1 = private unnamed_addr constant [6 x i8] c"(II)I\00", align 1
|
||||
@SX_JNI_CLS_max___II_I = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -282,6 +322,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [10 x i8] c"getWindow\00", align 1
|
||||
@str.1 = private unnamed_addr constant [21 x i8] c"()Ljava/lang/Object;\00", align 1
|
||||
@SX_JNI_CLS_getWindow____Ljava_lang_Object_ = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||
@g_should_call = internal global i1 false
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [5 x i8] c"noop\00", align 1
|
||||
@str.1 = private unnamed_addr constant [4 x i8] c"()V\00", align 1
|
||||
@SX_JNI_CLS_noop____V = internal global ptr null
|
||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -35,8 +60,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -60,7 +85,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -154,7 +194,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -162,7 +202,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -187,7 +227,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -283,6 +323,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@OBJC_SELECTOR_REFERENCES_init = internal global ptr null
|
||||
@OBJC_SELECTOR_REFERENCES_release = internal global ptr null
|
||||
@str = private unnamed_addr constant [4 x i8] c"ok\0A\00", align 1
|
||||
@@ -16,14 +17,38 @@ declare void @out(ptr) #0
|
||||
|
||||
declare ptr @malloc(i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
|
||||
declare ptr @memcpy(ptr, ptr, i64)
|
||||
|
||||
declare ptr @memset(ptr, i32, i64)
|
||||
|
||||
declare void @free(ptr)
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @CAllocator.alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca i64, align 8
|
||||
store i64 %1, ptr %allocaN, align 8
|
||||
%load = load i64, ptr %allocaN, align 8
|
||||
%call = call ptr @malloc(i64 %load)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @GPA.create(ptr sret({ ptr, ptr, ptr }), ptr) #0
|
||||
define internal void @CAllocator.dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
%alloca = alloca ptr, align 8
|
||||
store ptr %0, ptr %alloca, align 8
|
||||
%allocaN = alloca ptr, align 8
|
||||
store ptr %1, ptr %allocaN, align 8
|
||||
%load = load ptr, ptr %allocaN, align 8
|
||||
call void @free(ptr %load)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @GPA.init() #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||
@@ -38,8 +63,8 @@ entry:
|
||||
%add = add i64 %loadN, 1
|
||||
store i64 %add, ptr %gep, align 8
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%malloc = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %malloc
|
||||
%call = call ptr @malloc(i64 %loadN)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -63,7 +88,7 @@ entry:
|
||||
declare void @Arena.add_chunk(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @Arena.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @Arena.reset(ptr) #0
|
||||
@@ -78,7 +103,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
||||
declare void @Arena.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.create(ptr sret({ ptr, ptr, ptr }), ptr, ptr, i64) #0
|
||||
declare ptr @BufAlloc.init(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.reset(ptr) #0
|
||||
@@ -89,6 +114,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
||||
; Function Attrs: nounwind
|
||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.init(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare i64 @TrackingAllocator.leak_count(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.report(ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare ptr @TrackingAllocator.alloc(ptr, i64) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
declare void @TrackingAllocator.dealloc(ptr, ptr) #0
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||
entry:
|
||||
@@ -157,7 +197,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%2 = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
@@ -165,7 +205,7 @@ entry:
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -190,7 +230,7 @@ entry:
|
||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%3 = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN)
|
||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||
ret { ptr, i64 } %loadN
|
||||
}
|
||||
@@ -359,6 +399,20 @@ entry:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
%call = call ptr @CAllocator.alloc(ptr %0, i64 %1)
|
||||
ret ptr %call
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1) #0 {
|
||||
entry:
|
||||
call void @CAllocator.dealloc(ptr %0, ptr %1)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||
entry:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
53
tools/scratch.sh
Executable file
53
tools/scratch.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# tools/scratch.sh
|
||||
#
|
||||
# Quick interp/codegen parity test for a snippet.
|
||||
# Reads sx source from stdin, runs it under both `sx run` (interp)
|
||||
# and `sx build` + spawn (codegen), diffs the stdout output.
|
||||
#
|
||||
# Usage:
|
||||
# echo 'main :: () { print("hello\n"); }' | tools/scratch.sh
|
||||
# cat my_test.sx | tools/scratch.sh
|
||||
|
||||
set -u
|
||||
|
||||
SX="/Users/agra/projects/sx/zig-out/bin/sx"
|
||||
SRC=/tmp/scratch.sx
|
||||
BIN=/tmp/scratch-bin
|
||||
RUN_LOG=/tmp/scratch-run.log
|
||||
BUILD_LOG=/tmp/scratch-build.log
|
||||
|
||||
cat > "$SRC"
|
||||
|
||||
# Interp path
|
||||
"$SX" run "$SRC" > "$RUN_LOG" 2>&1
|
||||
RUN_EXIT=$?
|
||||
|
||||
# Codegen path
|
||||
if "$SX" build "$SRC" -o "$BIN" >> "$BUILD_LOG" 2>&1; then
|
||||
"$BIN" > "$BUILD_LOG" 2>&1
|
||||
BUILD_EXIT=$?
|
||||
else
|
||||
BUILD_EXIT=255
|
||||
fi
|
||||
|
||||
echo "── interp (sx run) exit=$RUN_EXIT ───────────────────────"
|
||||
cat "$RUN_LOG"
|
||||
echo ""
|
||||
echo "── codegen (sx build + spawn) exit=$BUILD_EXIT ─────────"
|
||||
cat "$BUILD_LOG"
|
||||
echo ""
|
||||
|
||||
if [[ "$RUN_EXIT" -ne "$BUILD_EXIT" ]]; then
|
||||
echo "DIVERGENCE: exit codes differ (run=$RUN_EXIT build=$BUILD_EXIT)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if diff -q "$RUN_LOG" "$BUILD_LOG" > /dev/null; then
|
||||
echo "PARITY: interp and codegen agree (exit $RUN_EXIT)"
|
||||
exit 0
|
||||
else
|
||||
echo "DIVERGENCE: stdout differs"
|
||||
diff "$RUN_LOG" "$BUILD_LOG" || true
|
||||
exit 1
|
||||
fi
|
||||
85
tools/verify-step.sh
Executable file
85
tools/verify-step.sh
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
# tools/verify-step.sh
|
||||
#
|
||||
# Single-command verification gate run after every plan step.
|
||||
# Per the mem.sx implementation plan, ~/projects/game must remain
|
||||
# buildable + runnable on all 3 platforms (macOS host, iOS sim,
|
||||
# Android device) at every step boundary.
|
||||
#
|
||||
# Exits 0 if all gates pass; non-zero on any failure.
|
||||
# Screenshots saved to /tmp/sx-game-{macos,iossim,android}.png.
|
||||
|
||||
set -e
|
||||
|
||||
ROOT="/Users/agra/projects/sx"
|
||||
GAME="/Users/agra/projects/game"
|
||||
SX="$ROOT/zig-out/bin/sx"
|
||||
|
||||
cd "$ROOT"
|
||||
|
||||
echo "── 1/5 zig build ─────────────────────────────────────"
|
||||
zig build
|
||||
|
||||
echo "── 2/5 zig build test ────────────────────────────────"
|
||||
zig build test
|
||||
|
||||
echo "── 3/5 example regression suite ──────────────────────"
|
||||
bash tests/run_examples.sh
|
||||
|
||||
echo "── 4/5 chess: cross-build for all 3 platforms ────────"
|
||||
# Builds must be serial — sx writes to .sx-tmp/ which would race in parallel.
|
||||
cd "$GAME"
|
||||
"$SX" build main.sx > /tmp/sx-game-macos-build.log 2>&1 \
|
||||
|| { echo "macOS build failed:"; cat /tmp/sx-game-macos-build.log; exit 1; }
|
||||
echo " macOS OK"
|
||||
"$SX" build --target ios-sim main.sx > /tmp/sx-game-iossim-build.log 2>&1 \
|
||||
|| { echo "iOS sim build failed:"; cat /tmp/sx-game-iossim-build.log; exit 1; }
|
||||
echo " iOS sim OK"
|
||||
"$SX" build --target android main.sx > /tmp/sx-game-android-build.log 2>&1 \
|
||||
|| { echo "Android build failed:"; cat /tmp/sx-game-android-build.log; exit 1; }
|
||||
echo " Android OK"
|
||||
|
||||
echo "── 5/5 chess: launch + screenshot on each platform ───"
|
||||
|
||||
# macOS — direct binary launch
|
||||
./sx-out/macos/SxChess > /tmp/sx-game-macos-run.log 2>&1 &
|
||||
PID=$!
|
||||
sleep 5
|
||||
if ps -p $PID > /dev/null; then
|
||||
screencapture -x /tmp/sx-game-macos.png
|
||||
kill $PID 2>/dev/null
|
||||
wait $PID 2>/dev/null
|
||||
echo " macOS screenshot saved: /tmp/sx-game-macos.png"
|
||||
else
|
||||
echo " macOS process exited early; log:"
|
||||
cat /tmp/sx-game-macos-run.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# iOS sim — requires booted simulator
|
||||
if xcrun simctl list devices booted 2>/dev/null | grep -q "Booted"; then
|
||||
xcrun simctl install booted "$GAME/sx-out/ios/SxChess.app" > /dev/null 2>&1
|
||||
xcrun simctl launch booted co.swipelab.sxchess > /dev/null 2>&1
|
||||
sleep 5
|
||||
xcrun simctl io booted screenshot /tmp/sx-game-iossim.png > /dev/null 2>&1
|
||||
echo " iOS sim screenshot saved: /tmp/sx-game-iossim.png"
|
||||
else
|
||||
echo " iOS sim SKIPPED (no booted simulator)"
|
||||
fi
|
||||
|
||||
# Android — requires connected device. Needs 6s+ for the side panel to render.
|
||||
if adb devices 2>/dev/null | grep -q "device$"; then
|
||||
adb install -r "$GAME/sx-out/android/sxchess.apk" > /dev/null 2>&1
|
||||
adb shell am force-stop co.swipelab.sxchess > /dev/null 2>&1
|
||||
adb shell am start -n co.swipelab.sxchess/.SxApp > /dev/null 2>&1
|
||||
sleep 6
|
||||
adb exec-out screencap -p > /tmp/sx-game-android.png 2>/dev/null
|
||||
echo " Android screenshot saved: /tmp/sx-game-android.png"
|
||||
else
|
||||
echo " Android SKIPPED (no connected device)"
|
||||
fi
|
||||
|
||||
cd "$ROOT"
|
||||
echo ""
|
||||
echo "═══ all gates pass ═════════════════════════════════════"
|
||||
echo "screenshots: /tmp/sx-game-{macos,iossim,android}.png"
|
||||
Reference in New Issue
Block a user