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(f32));
|
||||||
print("{}\n", size_of(Sx(f32)));
|
print("{}\n", size_of(Sx(f32)));
|
||||||
print("{}\n", size_of(Foo));
|
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);
|
print("listening on http://localhost:{}\n", PORT);
|
||||||
|
|
||||||
arena : Arena = ---;
|
arena := Arena.init(context.allocator, 65536);
|
||||||
arena_alloc := arena.create(context.allocator, 65536);
|
|
||||||
logger := Logger.{ prefix = "http", count = 0 };
|
logger := Logger.{ prefix = "http", count = 0 };
|
||||||
|
|
||||||
while true {
|
while true {
|
||||||
client := accept(fd, null, null);
|
client := accept(fd, null, null);
|
||||||
if client < 0 { continue; }
|
if client < 0 { continue; }
|
||||||
|
|
||||||
push Context.{ allocator = arena_alloc, data = xx @logger } {
|
push Context.{ allocator = xx arena, data = xx @logger } {
|
||||||
handle(client);
|
handle(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1141,6 +1141,12 @@ END;
|
|||||||
print("sizeof-f64: {}\n", size_of(f64));
|
print("sizeof-f64: {}\n", size_of(f64));
|
||||||
print("sizeof-struct: {}\n", size_of(Point));
|
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
|
// type_of + category matching
|
||||||
tv := 42;
|
tv := 42;
|
||||||
ttype := type_of(tv);
|
ttype := type_of(tv);
|
||||||
@@ -1667,29 +1673,30 @@ END;
|
|||||||
|
|
||||||
// ── GPA ─────────────────────────────────────────────────
|
// ── GPA ─────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
gpa_state : GPA = .{ alloc_count = 0 };
|
gpa := GPA.init();
|
||||||
gpa := gpa_state.create();
|
a : Allocator = xx gpa;
|
||||||
p1 := gpa.alloc(64);
|
p1 := a.alloc(64);
|
||||||
p2 := gpa.alloc(128);
|
p2 := a.alloc(128);
|
||||||
print("gpa allocs: {}\n", gpa_state.alloc_count); // gpa allocs: 2
|
print("gpa allocs: {}\n", gpa.alloc_count); // gpa allocs: 2
|
||||||
gpa.dealloc(p1);
|
a.dealloc(p1);
|
||||||
gpa.dealloc(p2);
|
a.dealloc(p2);
|
||||||
print("gpa final: {}\n", gpa_state.alloc_count); // gpa final: 0
|
print("gpa final: {}\n", gpa.alloc_count); // gpa final: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Arena backed by GPA (multi-chunk) ───────────────────
|
// ── Arena backed by GPA (multi-chunk) ───────────────────
|
||||||
{
|
{
|
||||||
gpa_state3 : GPA = .{ alloc_count = 0 };
|
gpa3 := GPA.init();
|
||||||
gpa3 := gpa_state3.create();
|
arena := Arena.init(xx gpa3, 32);
|
||||||
arena_state : Arena = ---;
|
a : Allocator = xx arena;
|
||||||
arena := arena_state.create(gpa3, 32);
|
|
||||||
// First chunk fits 80 usable bytes
|
// First chunk fits 80 usable bytes
|
||||||
a1 := arena.alloc(40);
|
a1 := a.alloc(40);
|
||||||
a2 := arena.alloc(40);
|
a2 := a.alloc(40);
|
||||||
print("arena chunks: {}\n", gpa_state3.alloc_count); // arena chunks: 1
|
// 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
|
// Overflow → new chunk
|
||||||
a3 := arena.alloc(16);
|
a3 := a.alloc(16);
|
||||||
print("arena overflow: {}\n", gpa_state3.alloc_count); // arena overflow: 2
|
print("arena overflow: {}\n", gpa3.alloc_count); // arena overflow: 3
|
||||||
// Verify memory works across chunks
|
// Verify memory works across chunks
|
||||||
p1 : [*]u8 = xx a1;
|
p1 : [*]u8 = xx a1;
|
||||||
p3 : [*]u8 = xx a3;
|
p3 : [*]u8 = xx a3;
|
||||||
@@ -1698,27 +1705,27 @@ END;
|
|||||||
print("arena a1: {}\n", p1[0]); // arena a1: 42
|
print("arena a1: {}\n", p1[0]); // arena a1: 42
|
||||||
print("arena a3: {}\n", p3[0]); // arena a3: 99
|
print("arena a3: {}\n", p3[0]); // arena a3: 99
|
||||||
// Reset retains newest chunk
|
// Reset retains newest chunk
|
||||||
arena_state.reset();
|
arena.reset();
|
||||||
print("arena reset idx: {}\n", arena_state.end_index); // arena reset idx: 0
|
print("arena reset idx: {}\n", arena.end_index); // arena reset idx: 0
|
||||||
print("arena reset gpa: {}\n", gpa_state3.alloc_count);// arena reset gpa: 1
|
print("arena reset gpa: {}\n", gpa3.alloc_count); // arena reset gpa: 2
|
||||||
// Deinit frees all
|
// Deinit frees all chunks + the Arena state itself
|
||||||
arena_state.deinit();
|
arena.deinit();
|
||||||
print("arena deinit: {}\n", gpa_state3.alloc_count); // arena deinit: 0
|
print("arena deinit: {}\n", gpa3.alloc_count); // arena deinit: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── BufAlloc from stack array ───────────────────────────
|
// ── BufAlloc from stack array ───────────────────────────
|
||||||
{
|
{
|
||||||
stack_buf : [128]u8 = ---;
|
stack_buf : [128]u8 = ---;
|
||||||
buf_state : BufAlloc = ---;
|
buf := BufAlloc.init(@stack_buf[0], 128);
|
||||||
bufalloc := buf_state.create(@stack_buf[0], 128);
|
a : Allocator = xx buf;
|
||||||
b1 := bufalloc.alloc(24);
|
b1 := a.alloc(24);
|
||||||
b2 := bufalloc.alloc(24);
|
b2 := a.alloc(24);
|
||||||
print("buf pos: {}\n", buf_state.pos); // buf pos: 48
|
print("buf pos: {}\n", buf.pos); // buf pos: 48
|
||||||
b3 := bufalloc.alloc(200);
|
b3 := a.alloc(200);
|
||||||
b3_i : s64 = xx b3;
|
b3_i : s64 = xx b3;
|
||||||
print("buf overflow: {}\n", b3_i); // buf overflow: 0
|
print("buf overflow: {}\n", b3_i); // buf overflow: 0
|
||||||
buf_state.reset();
|
buf.reset();
|
||||||
print("buf reset: {}\n", buf_state.pos); // buf reset: 0
|
print("buf reset: {}\n", buf.pos); // buf reset: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -2446,8 +2453,8 @@ END;
|
|||||||
step3 :: (a: Allocator) -> *void { a.alloc(8); }
|
step3 :: (a: Allocator) -> *void { a.alloc(8); }
|
||||||
step2 :: (a: Allocator) -> *void { step3(a); }
|
step2 :: (a: Allocator) -> *void { step3(a); }
|
||||||
step1 :: (a: Allocator) -> *void { step2(a); }
|
step1 :: (a: Allocator) -> *void { step2(a); }
|
||||||
gpa_e6 : GPA = .{ alloc_count = 0 };
|
gpa_e6 := GPA.init();
|
||||||
a_e6 : Allocator = xx @gpa_e6;
|
a_e6 : Allocator = xx gpa_e6;
|
||||||
ptr_e6 := step1(a_e6);
|
ptr_e6 := step1(a_e6);
|
||||||
print("closure-chain-call: {}\n", ptr_e6 != null);
|
print("closure-chain-call: {}\n", ptr_e6 != null);
|
||||||
a_e6.dealloc(ptr_e6);
|
a_e6.dealloc(ptr_e6);
|
||||||
@@ -2498,11 +2505,9 @@ END;
|
|||||||
print("closure-slice: {} {} {}\n", f_a7(0), f_a7(1), f_a7(2));
|
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)
|
// C5.L1: arena bulk free (closures allocated on arena, freed in bulk)
|
||||||
gpa_l1 : GPA = .{ alloc_count = 0 };
|
gpa_l1 := GPA.init();
|
||||||
a_l1 : Allocator = xx @gpa_l1;
|
arena_l1 := Arena.init(xx gpa_l1, 4096);
|
||||||
arena_l1 : Arena = ---;
|
push Context.{ allocator = xx arena_l1 } {
|
||||||
arena_alloc := arena_l1.create(a_l1, 4096);
|
|
||||||
push Context.{ allocator = arena_alloc } {
|
|
||||||
n_l1 : s32 = 5;
|
n_l1 : s32 = 5;
|
||||||
f_l1 := closure((x: s32) -> s32 => x + n_l1);
|
f_l1 := closure((x: s32) -> s32 => x + n_l1);
|
||||||
print("closure-arena: {}\n", f_l1(10));
|
print("closure-arena: {}\n", f_l1(10));
|
||||||
@@ -2510,8 +2515,8 @@ END;
|
|||||||
arena_l1.deinit();
|
arena_l1.deinit();
|
||||||
|
|
||||||
// C5.L2: GPA manual free (verify env alloc/dealloc)
|
// C5.L2: GPA manual free (verify env alloc/dealloc)
|
||||||
gpa_l2 : GPA = .{ alloc_count = 0 };
|
gpa_l2 := GPA.init();
|
||||||
a_l2 : Allocator = xx @gpa_l2;
|
a_l2 : Allocator = xx gpa_l2;
|
||||||
n_l2 : s32 = 7;
|
n_l2 : s32 = 7;
|
||||||
result_l2 : s32 = 0;
|
result_l2 : s32 = 0;
|
||||||
push Context.{ allocator = a_l2 } {
|
push Context.{ allocator = a_l2 } {
|
||||||
@@ -2660,8 +2665,8 @@ END;
|
|||||||
print("closure-neg: {}\n", neg_fn(val_e16));
|
print("closure-neg: {}\n", neg_fn(val_e16));
|
||||||
|
|
||||||
// C5.E17: closure with protocol value capture (#inline protocol)
|
// C5.E17: closure with protocol value capture (#inline protocol)
|
||||||
gpa_e17 : GPA = .{ alloc_count = 0 };
|
gpa_e17 := GPA.init();
|
||||||
a_e17 : Allocator = xx @gpa_e17;
|
a_e17 : Allocator = xx gpa_e17;
|
||||||
alloc_fn := closure((size: s64) -> *void => a_e17.alloc(size));
|
alloc_fn := closure((size: s64) -> *void => a_e17.alloc(size));
|
||||||
ptr_e17 := alloc_fn(32);
|
ptr_e17 := alloc_fn(32);
|
||||||
print("closure-proto-cap: {}\n", ptr_e17 != null);
|
print("closure-proto-cap: {}\n", ptr_e17 != null);
|
||||||
|
|||||||
@@ -6,10 +6,9 @@
|
|||||||
#import "modules/allocators.sx";
|
#import "modules/allocators.sx";
|
||||||
|
|
||||||
main :: () -> void {
|
main :: () -> void {
|
||||||
arena : Arena = ---;
|
arena := Arena.init(context.allocator, 4096);
|
||||||
arena.create(context.allocator, 4096);
|
|
||||||
|
|
||||||
new_ctx := Context.{ allocator = xx @arena, data = context.data };
|
new_ctx := Context.{ allocator = xx arena, data = context.data };
|
||||||
push new_ctx {
|
push new_ctx {
|
||||||
ptr := context.allocator.alloc(128);
|
ptr := context.allocator.alloc(128);
|
||||||
out("inside push\n");
|
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);
|
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) ---
|
// --- 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 {
|
GPA :: struct {
|
||||||
alloc_count: s64;
|
alloc_count: s64;
|
||||||
|
|
||||||
create :: (gpa: *GPA) -> Allocator {
|
init :: () -> *GPA {
|
||||||
xx gpa;
|
g : *GPA = xx libc_malloc(size_of(GPA));
|
||||||
|
g.alloc_count = 0;
|
||||||
|
g;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Allocator for GPA {
|
impl Allocator for GPA {
|
||||||
alloc :: (self: *GPA, size: s64) -> *void {
|
alloc :: (self: *GPA, size: s64) -> *void {
|
||||||
self.alloc_count += 1;
|
self.alloc_count += 1;
|
||||||
malloc(size);
|
return libc_malloc(size);
|
||||||
}
|
}
|
||||||
dealloc :: (self: *GPA, ptr: *void) {
|
dealloc :: (self: *GPA, ptr: *void) {
|
||||||
self.alloc_count -= 1;
|
self.alloc_count -= 1;
|
||||||
free(ptr);
|
libc_free(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Arena: multi-chunk bump allocator ---
|
// --- 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 {
|
ArenaChunk :: struct {
|
||||||
next: *ArenaChunk;
|
next: *ArenaChunk;
|
||||||
@@ -52,12 +85,13 @@ Arena :: struct {
|
|||||||
a.end_index = 0;
|
a.end_index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator {
|
init :: (parent_alloc: Allocator, size: s64) -> *Arena {
|
||||||
a.first = null;
|
self : *Arena = xx parent_alloc.alloc(size_of(Arena));
|
||||||
a.end_index = 0;
|
self.first = null;
|
||||||
a.parent = parent;
|
self.end_index = 0;
|
||||||
a.add_chunk(size);
|
self.parent = parent_alloc;
|
||||||
xx a;
|
self.add_chunk(size);
|
||||||
|
self;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset :: (a: *Arena) {
|
reset :: (a: *Arena) {
|
||||||
@@ -80,8 +114,8 @@ Arena :: struct {
|
|||||||
a.parent.dealloc(it);
|
a.parent.dealloc(it);
|
||||||
it = next;
|
it = next;
|
||||||
}
|
}
|
||||||
a.first = null;
|
parent := a.parent;
|
||||||
a.end_index = 0;
|
parent.dealloc(xx a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,17 +141,26 @@ impl Allocator for Arena {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- BufAlloc: bump allocator backed by a user-provided slice ---
|
// --- 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 {
|
BufAlloc :: struct {
|
||||||
buf: [*]u8;
|
buf: [*]u8;
|
||||||
len: s64;
|
len: s64;
|
||||||
pos: s64;
|
pos: s64;
|
||||||
|
|
||||||
create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator {
|
init :: (buf: [*]u8, len: s64) -> *BufAlloc {
|
||||||
b.buf = buf;
|
self_size :: size_of(BufAlloc);
|
||||||
b.len = len;
|
if len < self_size { return null; }
|
||||||
|
b : *BufAlloc = xx buf;
|
||||||
|
b.buf = @buf[self_size];
|
||||||
|
b.len = len - self_size;
|
||||||
b.pos = 0;
|
b.pos = 0;
|
||||||
xx b;
|
b;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset :: (b: *BufAlloc) {
|
reset :: (b: *BufAlloc) {
|
||||||
@@ -137,3 +180,66 @@ impl Allocator for BufAlloc {
|
|||||||
}
|
}
|
||||||
dealloc :: (self: *BufAlloc, ptr: *void) {}
|
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;
|
// sin :: (x: $T) -> T #builtin;
|
||||||
// cos :: (x: $T) -> T #builtin;
|
// cos :: (x: $T) -> T #builtin;
|
||||||
size_of :: ($T: Type) -> s64 #builtin;
|
size_of :: ($T: Type) -> s64 #builtin;
|
||||||
malloc :: (size: s64) -> *void #builtin;
|
align_of :: ($T: Type) -> s64 #builtin;
|
||||||
memcpy :: (dst: *void, src: *void, size: s64) -> *void #builtin;
|
// Low-level libc bindings, used by allocator implementations to avoid
|
||||||
memset :: (dst: *void, val: s64, size: s64) -> void #builtin;
|
// recursing through `context.allocator`.
|
||||||
free :: (ptr: *void) -> void #builtin;
|
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_of :: (val: $T) -> Type #builtin;
|
||||||
type_name :: ($T: Type) -> string #builtin;
|
type_name :: ($T: Type) -> string #builtin;
|
||||||
field_count :: ($T: Type) -> s64 #builtin;
|
field_count :: ($T: Type) -> s64 #builtin;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#import "std.sx";
|
||||||
|
|
||||||
assert :: (condition: bool) {
|
assert :: (condition: bool) {
|
||||||
if !condition {
|
if !condition {
|
||||||
out("assertion failed\n");
|
out("assertion failed\n");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#import "modules/ui/types.sx";
|
#import "modules/ui/types.sx";
|
||||||
#import "modules/ui/render.sx";
|
#import "modules/ui/render.sx";
|
||||||
#import "modules/ui/events.sx";
|
#import "modules/ui/events.sx";
|
||||||
|
#import "modules/ui/font.sx";
|
||||||
#import "modules/ui/view.sx";
|
#import "modules/ui/view.sx";
|
||||||
#import "modules/ui/renderer.sx";
|
#import "modules/ui/renderer.sx";
|
||||||
|
|
||||||
@@ -17,9 +18,11 @@ UIPipeline :: struct {
|
|||||||
root: ViewChild;
|
root: ViewChild;
|
||||||
has_root: bool;
|
has_root: bool;
|
||||||
|
|
||||||
// Frame arena infrastructure
|
// Frame arena infrastructure. Both arenas are typed `*Arena`
|
||||||
arena_a: Arena;
|
// pointers (per the init-returns-typed-pointer API in
|
||||||
arena_b: Arena;
|
// allocators.sx). Cast to Allocator at use sites via `xx arena_a`.
|
||||||
|
arena_a: *Arena;
|
||||||
|
arena_b: *Arena;
|
||||||
frame_index: s64;
|
frame_index: s64;
|
||||||
body: Closure() -> View;
|
body: Closure() -> View;
|
||||||
has_body: bool;
|
has_body: bool;
|
||||||
@@ -65,8 +68,8 @@ UIPipeline :: struct {
|
|||||||
self.has_body = true;
|
self.has_body = true;
|
||||||
self.parent_allocator = context.allocator;
|
self.parent_allocator = context.allocator;
|
||||||
// Initialize both arenas (256KB initial, grows automatically)
|
// Initialize both arenas (256KB initial, grows automatically)
|
||||||
self.arena_a.create(self.parent_allocator, 262144);
|
self.arena_a = Arena.init(self.parent_allocator, 262144);
|
||||||
self.arena_b.create(self.parent_allocator, 262144);
|
self.arena_b = Arena.init(self.parent_allocator, 262144);
|
||||||
self.frame_index = 0;
|
self.frame_index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +132,7 @@ UIPipeline :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tick_with_body :: (self: *UIPipeline) {
|
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();
|
build_arena.reset();
|
||||||
|
|
||||||
// Reset render_tree nodes (backing is stale after 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,
|
/// A resolved module: the fully-resolved declarations of a single .sx file,
|
||||||
/// with its own scope tracking which names are defined.
|
/// 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 {
|
pub const ResolvedModule = struct {
|
||||||
path: []const u8,
|
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: []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),
|
scope: std.StringHashMap(void),
|
||||||
|
|
||||||
/// Try to add a declaration. Returns true if added, false if name already in scope.
|
/// Add a declaration authored in this file. Updates scope + own_decls +
|
||||||
pub fn addDecl(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), decl: *Node) !bool {
|
/// 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 (decl.data.declName()) |name| {
|
||||||
if (self.scope.contains(name)) return false;
|
if (self.scope.contains(name)) return false;
|
||||||
try self.scope.put(name, {});
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Merge another module's decls as flat imports (skipping duplicates).
|
/// Flat-import another module. The imported names are NOT added to
|
||||||
pub fn mergeFlat(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), other: ResolvedModule) !void {
|
/// `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| {
|
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.
|
/// Add another module as a namespaced import. The alias `name` becomes
|
||||||
pub fn addNamespace(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), name: []const u8, other: ResolvedModule, span: ast.Span) !void {
|
/// 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);
|
const ns_node = try allocator.create(Node);
|
||||||
ns_node.* = .{
|
ns_node.* = .{
|
||||||
.span = span,
|
.span = span,
|
||||||
@@ -287,11 +355,19 @@ pub const ResolvedModule = struct {
|
|||||||
} },
|
} },
|
||||||
};
|
};
|
||||||
try self.scope.put(name, {});
|
try self.scope.put(name, {});
|
||||||
|
try seen_list.put(name, {});
|
||||||
try list.append(allocator, ns_node);
|
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.decls = try list.toOwnedSlice(allocator);
|
||||||
|
self.own_decls = try own_list.toOwnedSlice(allocator);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -323,6 +399,7 @@ pub fn resolveImports(
|
|||||||
var mod = ResolvedModule{
|
var mod = ResolvedModule{
|
||||||
.path = file_path,
|
.path = file_path,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
|
.own_decls = &.{},
|
||||||
.scope = std.StringHashMap(void).init(allocator),
|
.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);
|
const flat_decls = try flattenComptimeConditionals(allocator, root.data.root.decls, comptime_ctx);
|
||||||
|
|
||||||
var decl_list = std.ArrayList(*Node).empty;
|
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| {
|
for (flat_decls) |decl| {
|
||||||
if (decl.data == .c_import_decl) {
|
if (decl.data == .c_import_decl) {
|
||||||
@@ -397,21 +479,23 @@ pub fn resolveImports(
|
|||||||
};
|
};
|
||||||
ns_node.source_file = file_path;
|
ns_node.source_file = file_path;
|
||||||
try mod.scope.put(ns_name, {});
|
try mod.scope.put(ns_name, {});
|
||||||
|
try seen_in_list.put(ns_name, {});
|
||||||
try decl_list.append(allocator, ns_node);
|
try decl_list.append(allocator, ns_node);
|
||||||
|
try own_decl_list.append(allocator, ns_node);
|
||||||
} else {
|
} else {
|
||||||
// Flat: add fn_decls directly + keep c_import_decl
|
// Flat: add fn_decls directly + keep c_import_decl
|
||||||
for (result.fn_decls) |fd| {
|
for (result.fn_decls) |fd| {
|
||||||
fd.source_file = file_path;
|
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;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (decl.data != .import_decl) {
|
if (decl.data != .import_decl) {
|
||||||
decl.source_file = file_path;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
const imp = decl.data.import_decl;
|
const imp = decl.data.import_decl;
|
||||||
@@ -473,13 +557,13 @@ pub fn resolveImports(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (imp.name) |ns_name| {
|
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 {
|
} 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;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,13 +608,20 @@ fn resolveDirectoryImport(
|
|||||||
try chain.put(dir_path, {});
|
try chain.put(dir_path, {});
|
||||||
defer _ = chain.remove(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{
|
var combined = ResolvedModule{
|
||||||
.path = dir_path,
|
.path = dir_path,
|
||||||
.decls = &.{},
|
.decls = &.{},
|
||||||
|
.own_decls = &.{},
|
||||||
.scope = std.StringHashMap(void).init(allocator),
|
.scope = std.StringHashMap(void).init(allocator),
|
||||||
};
|
};
|
||||||
var decl_list = std.ArrayList(*Node).empty;
|
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| {
|
for (file_names.items) |file_name| {
|
||||||
const file_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ dir_path, 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;
|
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;
|
return combined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -681,32 +681,40 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize vtable globals with function pointer constants.
|
/// Initialize vtable + aggregate-with-func_ref globals with function
|
||||||
/// Must run after Pass 1 (function declarations) so func_map is populated.
|
/// 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 {
|
fn initVtableGlobals(self: *LLVMEmitter) void {
|
||||||
for (self.ir_mod.globals.items, 0..) |global, i| {
|
for (self.ir_mod.globals.items, 0..) |global, i| {
|
||||||
const iv = global.init_val orelse continue;
|
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_global = self.global_map.get(@intCast(i)) orelse continue;
|
||||||
const llvm_ty = self.toLLVMType(global.ty);
|
const llvm_ty = self.toLLVMType(global.ty);
|
||||||
|
|
||||||
// Build constant struct of function pointers
|
switch (iv) {
|
||||||
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
.vtable => |func_ids| {
|
||||||
defer field_vals.deinit(self.alloc);
|
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
||||||
for (func_ids) |fid| {
|
defer field_vals.deinit(self.alloc);
|
||||||
const llvm_func = self.func_map.get(fid.index()) orelse {
|
for (func_ids) |fid| {
|
||||||
field_vals.append(self.alloc, c.LLVMConstNull(self.cached_ptr)) catch unreachable;
|
const llvm_func = self.func_map.get(fid.index()) orelse {
|
||||||
continue;
|
field_vals.append(self.alloc, c.LLVMConstNull(self.cached_ptr)) catch unreachable;
|
||||||
};
|
continue;
|
||||||
field_vals.append(self.alloc, llvm_func) catch unreachable;
|
};
|
||||||
|
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| {
|
.call_builtin => |bi| {
|
||||||
// Builtins that map to libc functions or LLVM intrinsics
|
// Builtins that map to libc functions or LLVM intrinsics
|
||||||
switch (bi.builtin) {
|
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 => {
|
.sqrt, .sin, .cos, .floor => {
|
||||||
const val = self.resolveRef(bi.args[0]);
|
const val = self.resolveRef(bi.args[0]);
|
||||||
const val_ty = c.LLVMTypeOf(val);
|
const val_ty = c.LLVMTypeOf(val);
|
||||||
@@ -3699,6 +3674,7 @@ pub const LLVMEmitter = struct {
|
|||||||
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
||||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
.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),
|
else => c.LLVMConstNull(elem_ty),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -352,11 +352,8 @@ pub const BuiltinId = enum(u16) {
|
|||||||
cos,
|
cos,
|
||||||
floor,
|
floor,
|
||||||
size_of,
|
size_of,
|
||||||
|
align_of,
|
||||||
cast,
|
cast,
|
||||||
malloc,
|
|
||||||
free,
|
|
||||||
memcpy,
|
|
||||||
memset,
|
|
||||||
type_of,
|
type_of,
|
||||||
alloc,
|
alloc,
|
||||||
dealloc,
|
dealloc,
|
||||||
@@ -519,5 +516,10 @@ pub const ConstantValue = union(enum) {
|
|||||||
aggregate: []const ConstantValue,
|
aggregate: []const ConstantValue,
|
||||||
/// Vtable constant: struct of function pointers, used for protocol vtable globals.
|
/// Vtable constant: struct of function pointers, used for protocol vtable globals.
|
||||||
vtable: []const FuncId,
|
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),
|
.boolean => |b| @intFromBool(b),
|
||||||
.null_val => 0,
|
.null_val => 0,
|
||||||
.heap_ptr => |hp| blk: {
|
.heap_ptr => |hp| blk: {
|
||||||
const mem = self.heapSlice(hp) orelse return error.TypeError;
|
// `heapSlice` returns the slice already advanced by `hp.offset`,
|
||||||
break :blk @intFromPtr(mem.ptr) + 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: {
|
.string => |s| blk: {
|
||||||
const buf = try self.alloc.alloc(u8, s.len + 1);
|
const buf = try self.alloc.alloc(u8, s.len + 1);
|
||||||
@@ -1315,6 +1318,7 @@ pub const Interpreter = struct {
|
|||||||
}
|
}
|
||||||
return .{ .aggregate = fields };
|
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 {
|
fn execBuiltinInner(self: *Interpreter, bi: inst_mod.BuiltinCall, frame: *Frame) InterpError!ExecResult {
|
||||||
switch (bi.builtin) {
|
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 => {
|
.out => {
|
||||||
const str_val = frame.getRef(bi.args[0]);
|
const str_val = frame.getRef(bi.args[0]);
|
||||||
if (str_val.asString(self)) |s| {
|
if (str_val.asString(self)) |s| {
|
||||||
@@ -1462,6 +1416,9 @@ pub const Interpreter = struct {
|
|||||||
// Return a default size (8 bytes for most types)
|
// Return a default size (8 bytes for most types)
|
||||||
return .{ .value = .{ .int = 8 } };
|
return .{ .value = .{ .int = 8 } };
|
||||||
},
|
},
|
||||||
|
.align_of => {
|
||||||
|
return .{ .value = .{ .int = 8 } };
|
||||||
|
},
|
||||||
.sqrt => {
|
.sqrt => {
|
||||||
const val = frame.getRef(bi.args[0]);
|
const val = frame.getRef(bi.args[0]);
|
||||||
const f = val.asFloat() orelse return error.TypeError;
|
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,
|
name: []const u8,
|
||||||
param_types: []const TypeId, // excluding self
|
param_types: []const TypeId, // excluding self
|
||||||
ret_type: TypeId,
|
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`).
|
/// 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);
|
self.scanDecls(decls);
|
||||||
// Pass 1b: inject compile-time constants (OS, ARCH, POINTER_SIZE) from target config
|
// Pass 1b: inject compile-time constants (OS, ARCH, POINTER_SIZE) from target config
|
||||||
self.injectComptimeConstants();
|
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)
|
// Pass 2: lower main (and comptime side-effects)
|
||||||
self.lowerMainAndComptime(decls);
|
self.lowerMainAndComptime(decls);
|
||||||
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
|
// 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) {
|
} else if (cd.value.data == .union_decl) {
|
||||||
// Register plain union types in the type table
|
// Register plain union types in the type table
|
||||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
_ = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||||
} else if (cd.value.data == .type_expr) {
|
} else if (cd.value.data == .type_expr or
|
||||||
// Type alias: MyFloat :: f64; → register MyFloat as alias for f64
|
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);
|
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types);
|
||||||
self.type_alias_map.put(cd.name, target_ty) catch {};
|
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)
|
// Handle generic struct instantiation: Vec3 :: Vec(3, f32)
|
||||||
// Parser produces a .call node for these (not parameterized_type_expr)
|
// 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
|
// 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) return true;
|
||||||
if (fd.body.data.foreign_expr.library_ref != null) 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 scopes = self.module_scopes orelse return true;
|
||||||
const source = self.current_source_file orelse return true;
|
const source = self.current_source_file orelse return true;
|
||||||
const scope = scopes.get(source) orelse return true;
|
const own_scope = scopes.get(source) orelse return true;
|
||||||
return scope.contains(fn_name);
|
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
|
/// 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;
|
if (self.lowered_functions.contains(name)) return;
|
||||||
// No AST? (builtins, foreign functions, or imported functions not in this file)
|
// No AST? (builtins, foreign functions, or imported functions not in this file)
|
||||||
const fd = self.fn_ast_map.get(name) orelse return;
|
const fd = self.fn_ast_map.get(name) orelse return;
|
||||||
// Check builtin/foreign/generic — these stay as extern stubs
|
// Foreign declarations stay as extern stubs but need to be REGISTERED
|
||||||
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr or fd.body.data == .compiler_expr) return;
|
// 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)
|
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.
|
// 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)
|
// Check module-level value constants (e.g. AF_INET :s32: 2)
|
||||||
if (self.module_const_map.get(id.name)) |ci| {
|
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);
|
break :blk self.emitModuleConst(ci);
|
||||||
}
|
}
|
||||||
// Check if it's a function name — produce function pointer reference
|
// Check if it's a function name — produce function pointer reference
|
||||||
// Resolve mangled name for block-local functions
|
// 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;
|
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)) {
|
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
|
// Type-as-value: if target is Any (Type variable), produce a type name string
|
||||||
if (self.target_type == .any) {
|
if (self.target_type == .any) {
|
||||||
const fd = self.fn_ast_map.get(eff_fn_name).?;
|
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});
|
d.addFmt(.err, c.callee.span, "C function '{s}' not visible; add #import for the module that declares it", .{eff_name});
|
||||||
return Ref.none;
|
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.fn_ast_map.get(eff_name)) |fd| {
|
||||||
if (self.current_match_tags) |tags| {
|
if (self.current_match_tags) |tags| {
|
||||||
if (tags.len > 0 and self.hasCastWithRuntimeType(c)) {
|
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)
|
// Check builtins first (these are handled natively by interpreter and emitter)
|
||||||
if (resolveBuiltin(id.name)) |bid| {
|
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) {
|
const ret_ty: TypeId = switch (bid) {
|
||||||
.malloc => .s64, // pointer
|
.size_of, .align_of => .s64,
|
||||||
.size_of => .s64,
|
|
||||||
.memcpy, .memset => .s64,
|
|
||||||
.sqrt, .sin, .cos, .floor => blk: {
|
.sqrt, .sin, .cos, .floor => blk: {
|
||||||
// Math builtins: return type matches argument type ($T -> T)
|
// Math builtins: return type matches argument type ($T -> T)
|
||||||
if (c.args.len > 0) {
|
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
|
// Try non-generic qualified method
|
||||||
if (self.fn_ast_map.get(qualified)) |fd| {
|
if (self.fn_ast_map.get(qualified)) |fd| {
|
||||||
if (!self.lowered_functions.contains(qualified)) {
|
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,
|
/// Pattern-match `context.allocator.alloc(size)` → heap_alloc,
|
||||||
/// `context.allocator.dealloc(ptr)` → heap_free.
|
/// `context.allocator.dealloc(ptr)` → heap_free.
|
||||||
fn matchContextAllocCall(self: *Lowering, fa: ast.FieldAccess, call_args: []const Ref) ?Ref {
|
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 },
|
.{ "cos", inst_mod.BuiltinId.cos },
|
||||||
.{ "floor", inst_mod.BuiltinId.floor },
|
.{ "floor", inst_mod.BuiltinId.floor },
|
||||||
.{ "size_of", inst_mod.BuiltinId.size_of },
|
.{ "size_of", inst_mod.BuiltinId.size_of },
|
||||||
|
.{ "align_of", inst_mod.BuiltinId.align_of },
|
||||||
.{ "cast", inst_mod.BuiltinId.cast },
|
.{ "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| {
|
inline for (builtins) |entry| {
|
||||||
if (std.mem.eql(u8, name, entry[0])) return entry[1];
|
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_byte_size_inner = self.computeEnvSize(capture_list);
|
||||||
const env_size_val = self.builder.constInt(@intCast(env_byte_size_inner), .s64);
|
const env_size_val = self.builder.constInt(@intCast(env_byte_size_inner), .s64);
|
||||||
// memcpy(local_alloca, env_param, size)
|
// memcpy(local_alloca, env_param, size)
|
||||||
const cp_args = self.alloc.dupe(Ref, &.{ env_local, env_param_ref, env_size_val }) catch unreachable;
|
_ = self.callForeign("memcpy", &.{ env_local, env_param_ref, env_size_val }, self.module.types.ptrTo(.void));
|
||||||
_ = self.builder.emit(.{ .call_builtin = .{
|
|
||||||
.builtin = inst_mod.BuiltinId.memcpy,
|
|
||||||
.args = cp_args,
|
|
||||||
} }, self.module.types.ptrTo(.void));
|
|
||||||
|
|
||||||
for (capture_list, 0..) |cap, i| {
|
for (capture_list, 0..) |cap, i| {
|
||||||
// GEP into env struct to get field pointer
|
// 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 ptr_void = self.module.types.ptrTo(.void);
|
||||||
const env_heap = self.builder.emit(.{ .heap_alloc = .{ .operand = env_size } }, ptr_void);
|
const env_heap = self.builder.emit(.{ .heap_alloc = .{ .operand = env_size } }, ptr_void);
|
||||||
// memcpy(heap, stack_alloca, size)
|
// memcpy(heap, stack_alloca, size)
|
||||||
const args = self.alloc.dupe(Ref, &.{ env_heap, env_local, env_size }) catch unreachable;
|
_ = self.callForeign("memcpy", &.{ env_heap, env_local, env_size }, ptr_void);
|
||||||
_ = self.builder.emit(.{ .call_builtin = .{
|
|
||||||
.builtin = inst_mod.BuiltinId.memcpy,
|
|
||||||
.args = args,
|
|
||||||
} }, ptr_void);
|
|
||||||
|
|
||||||
return self.builder.closureCreate(func_id, env_heap, closure_ty);
|
return self.builder.closureCreate(func_id, env_heap, closure_ty);
|
||||||
} else {
|
} else {
|
||||||
@@ -6369,31 +6539,26 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
// ── Generic monomorphization ──────────────────────────────────
|
// ── Generic monomorphization ──────────────────────────────────
|
||||||
|
|
||||||
/// Lower a call to a generic function by monomorphizing it with inferred type arguments.
|
/// Build `tp.name -> TypeId` bindings for a generic call.
|
||||||
fn lowerGenericCall(self: *Lowering, fd: *const ast.FnDecl, base_name: []const u8, call_node: *const ast.Call, lowered_args: []Ref) Ref {
|
/// `args_ast` must be parallel to `fd.params`; for dot-calls the caller
|
||||||
// Infer type param bindings from call arguments
|
/// 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);
|
var bindings = std.StringHashMap(TypeId).init(self.alloc);
|
||||||
defer bindings.deinit();
|
const types_passed_explicitly = args_ast.len == fd.params.len;
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
for (fd.type_params) |tp| {
|
for (fd.type_params) |tp| {
|
||||||
var found = false;
|
var found = false;
|
||||||
|
// Strategy 1: explicit — the param whose name matches `tp.name` IS
|
||||||
// Strategy 1: Direct type param declaration ($T: Type)
|
// the `$T: Type` declaration; the arg at that position is a type expression.
|
||||||
// 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.
|
|
||||||
if (types_passed_explicitly) {
|
if (types_passed_explicitly) {
|
||||||
for (fd.params, 0..) |param, pi| {
|
for (fd.params, 0..) |param, pi| {
|
||||||
if (std.mem.eql(u8, param.name, tp.name)) {
|
if (std.mem.eql(u8, param.name, tp.name)) {
|
||||||
// This param IS the type param declaration
|
if (pi < args_ast.len and type_bridge.isTypeShapedAstNode(args_ast[pi], &self.module.types)) {
|
||||||
if (pi < call_node.args.len) {
|
const ty = self.resolveTypeArg(args_ast[pi]);
|
||||||
const ty = self.resolveTypeArg(call_node.args[pi]);
|
|
||||||
bindings.put(tp.name, ty) catch {};
|
bindings.put(tp.name, ty) catch {};
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
@@ -6402,11 +6567,8 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (found) continue;
|
if (found) continue;
|
||||||
|
// Strategy 2: infer from value params that USE the type param
|
||||||
// Strategy 2: Infer from params that USE the type param (e.g., a: $T, b: T, items: []$T)
|
// (e.g. a: $T, b: T, items: []$T). Pick widest type across matches.
|
||||||
// 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.
|
|
||||||
var inferred_ty: ?TypeId = null;
|
var inferred_ty: ?TypeId = null;
|
||||||
var s2_arg_idx: usize = 0;
|
var s2_arg_idx: usize = 0;
|
||||||
for (fd.params) |param| {
|
for (fd.params) |param| {
|
||||||
@@ -6420,8 +6582,8 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
const matched = self.matchTypeParam(param.type_expr, tp.name);
|
const matched = self.matchTypeParam(param.type_expr, tp.name);
|
||||||
if (matched) {
|
if (matched) {
|
||||||
if (s2_arg_idx < call_node.args.len) {
|
if (s2_arg_idx < args_ast.len) {
|
||||||
const arg_ty = self.inferExprType(call_node.args[s2_arg_idx]);
|
const arg_ty = self.inferExprType(args_ast[s2_arg_idx]);
|
||||||
const extracted = self.extractTypeParam(param.type_expr, arg_ty, tp.name);
|
const extracted = self.extractTypeParam(param.type_expr, arg_ty, tp.name);
|
||||||
if (extracted) |ety| {
|
if (extracted) |ety| {
|
||||||
if (inferred_ty) |prev| {
|
if (inferred_ty) |prev| {
|
||||||
@@ -6441,8 +6603,17 @@ pub const Lowering = struct {
|
|||||||
bindings.put(tp.name, ty) catch {};
|
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_buf: [256]u8 = undefined;
|
||||||
var mangled_len: usize = 0;
|
var mangled_len: usize = 0;
|
||||||
for (base_name) |ch| {
|
for (base_name) |ch| {
|
||||||
@@ -6452,14 +6623,12 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (fd.type_params) |tp| {
|
for (fd.type_params) |tp| {
|
||||||
// Append separator
|
|
||||||
for ("__") |ch| {
|
for ("__") |ch| {
|
||||||
if (mangled_len < mangled_buf.len) {
|
if (mangled_len < mangled_buf.len) {
|
||||||
mangled_buf[mangled_len] = ch;
|
mangled_buf[mangled_len] = ch;
|
||||||
mangled_len += 1;
|
mangled_len += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Append type name
|
|
||||||
const ty = bindings.get(tp.name) orelse .s64;
|
const ty = bindings.get(tp.name) orelse .s64;
|
||||||
const type_name_str = self.mangleTypeName(ty);
|
const type_name_str = self.mangleTypeName(ty);
|
||||||
for (type_name_str) |ch| {
|
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)) {
|
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);
|
self.monomorphizeFunction(fd, mangled_name, &bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the monomorphized function and call it (stripping type args)
|
|
||||||
if (self.resolveFuncByName(mangled_name)) |fid| {
|
if (self.resolveFuncByName(mangled_name)) |fid| {
|
||||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
const ret_ty = func.ret;
|
const ret_ty = func.ret;
|
||||||
const params = func.params;
|
const params = func.params;
|
||||||
// Build value-only args (skip type param declaration args)
|
// 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;
|
var value_args = std.ArrayList(Ref).empty;
|
||||||
defer value_args.deinit(self.alloc);
|
defer value_args.deinit(self.alloc);
|
||||||
var arg_idx: usize = 0;
|
var arg_idx: usize = 0;
|
||||||
for (fd.params) |p| {
|
for (fd.params) |p| {
|
||||||
if (isTypeParamDecl(&p, fd.type_params)) {
|
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;
|
if (types_passed_explicitly) arg_idx += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -6894,6 +7067,11 @@ pub const Lowering = struct {
|
|||||||
const size: i64 = @intCast(self.typeSizeBytes(ty));
|
const size: i64 = @intCast(self.typeSizeBytes(ty));
|
||||||
return self.builder.constInt(size, .s64);
|
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")) {
|
if (std.mem.eql(u8, name, "field_count")) {
|
||||||
// field_count(T) → const_int(N)
|
// field_count(T) → const_int(N)
|
||||||
const ty = self.resolveTypeArg(c.args[0]);
|
const ty = self.resolveTypeArg(c.args[0]);
|
||||||
@@ -7019,9 +7197,13 @@ pub const Lowering = struct {
|
|||||||
if (self.type_bindings) |tb| {
|
if (self.type_bindings) |tb| {
|
||||||
if (tb.get(id.name)) |ty| return ty;
|
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);
|
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| {
|
.type_expr => |te| {
|
||||||
if (self.type_alias_map.get(te.name)) |alias_ty| return alias_ty;
|
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))
|
// Handle type constructor calls: size_of(Sx(f32)), size_of(Complex(u32))
|
||||||
return self.resolveTypeCallWithBindings(&cl);
|
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,
|
else => return .s64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7498,6 +7688,41 @@ pub const Lowering = struct {
|
|||||||
// skipping the first param (self) since it's prepended later.
|
// skipping the first param (self) since it's prepended later.
|
||||||
if (c.callee.data == .field_access) {
|
if (c.callee.data == .field_access) {
|
||||||
const fa = 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);
|
const obj_ty = self.inferExprType(fa.object);
|
||||||
// Protocol-typed receiver: look up the method on the protocol decl. The
|
// Protocol-typed receiver: look up the method on the protocol decl. The
|
||||||
// protocol's ProtocolMethodInfo.param_types already excludes self.
|
// protocol's ProtocolMethodInfo.param_types already excludes self.
|
||||||
@@ -8511,9 +8736,11 @@ pub const Lowering = struct {
|
|||||||
};
|
};
|
||||||
ptypes.append(self.alloc, pty) catch unreachable;
|
ptypes.append(self.alloc, pty) catch unreachable;
|
||||||
}
|
}
|
||||||
|
var ret_is_self = false;
|
||||||
const ret = if (method.return_type) |rt| blk: {
|
const ret = if (method.return_type) |rt| blk: {
|
||||||
if (rt.data == .type_expr) {
|
if (rt.data == .type_expr) {
|
||||||
if (std.mem.eql(u8, rt.data.type_expr.name, "Self")) {
|
if (std.mem.eql(u8, rt.data.type_expr.name, "Self")) {
|
||||||
|
ret_is_self = true;
|
||||||
break :blk void_ptr_ty;
|
break :blk void_ptr_ty;
|
||||||
}
|
}
|
||||||
if (self.type_alias_map.get(rt.data.type_expr.name)) |aliased| {
|
if (self.type_alias_map.get(rt.data.type_expr.name)) |aliased| {
|
||||||
@@ -8526,6 +8753,7 @@ pub const Lowering = struct {
|
|||||||
.name = method.name,
|
.name = method.name,
|
||||||
.param_types = self.alloc.dupe(TypeId, ptypes.items) catch unreachable,
|
.param_types = self.alloc.dupe(TypeId, ptypes.items) catch unreachable,
|
||||||
.ret_type = ret,
|
.ret_type = ret,
|
||||||
|
.ret_is_self = ret_is_self,
|
||||||
}) catch unreachable;
|
}) catch unreachable;
|
||||||
}
|
}
|
||||||
self.protocol_decl_map.put(pd.name, .{
|
self.protocol_decl_map.put(pd.name, .{
|
||||||
@@ -8799,6 +9027,54 @@ pub const Lowering = struct {
|
|||||||
return owned;
|
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
|
/// Create a thunk function: __thunk_ConcreteType_Protocol_method(ctx: *void, args...) -> ret
|
||||||
/// The thunk calls ConcreteType.method(ctx, args...).
|
/// The thunk calls ConcreteType.method(ctx, args...).
|
||||||
fn createProtocolThunk(self: *Lowering, proto_name: []const u8, concrete_type_name: []const u8, method: ProtocolMethodInfo) FuncId {
|
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) {
|
if (heap_copy) {
|
||||||
const concrete_size = self.module.types.typeSizeBytes(concrete_ty);
|
const concrete_size = self.module.types.typeSizeBytes(concrete_ty);
|
||||||
const size_ref = self.builder.constInt(@intCast(concrete_size), .s64);
|
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 heap_ptr = self.allocViaContext(size_ref, void_ptr_ty);
|
||||||
const memcpy_args = self.alloc.dupe(Ref, &.{ heap_ptr, concrete_ptr, size_ref }) catch unreachable;
|
_ = self.callForeign("memcpy", &.{ heap_ptr, concrete_ptr, size_ref }, void_ptr_ty);
|
||||||
_ = self.builder.emit(.{ .call_builtin = .{
|
|
||||||
.builtin = inst_mod.BuiltinId.memcpy,
|
|
||||||
.args = memcpy_args,
|
|
||||||
} }, void_ptr_ty);
|
|
||||||
ctx_ptr = heap_ptr;
|
ctx_ptr = heap_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9055,12 +9327,11 @@ pub const Lowering = struct {
|
|||||||
const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
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);
|
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,
|
// If the protocol method was declared `-> Self` (encoded here as *void)
|
||||||
// unbox: load the concrete value from the returned pointer. Real pointer
|
// and the caller expects a value type, unbox: load the concrete value
|
||||||
// returns (declared `-> *T` for non-Self T) are NOT auto-loaded — the
|
// from the returned pointer. A literal `-> *void` return is NOT
|
||||||
// pointee may be a single byte and reading `sizeof(target)` past it
|
// auto-loaded — it's a real pointer whose pointee size we don't know.
|
||||||
// segfaults. Self is encoded as `*void`, so test against that exact type.
|
if (mi.ret_is_self) {
|
||||||
if (mi.ret_type == void_ptr) {
|
|
||||||
if (self.target_type) |target| {
|
if (self.target_type) |target| {
|
||||||
const target_info = self.module.types.get(target);
|
const target_info = self.module.types.get(target);
|
||||||
if (target_info != .pointer) {
|
if (target_info != .pointer) {
|
||||||
@@ -9149,7 +9420,7 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
break :blk TypeId.f64;
|
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,
|
.cast => if (c.args.len > 0) self.resolveTypeArg(c.args[0]) else .s64,
|
||||||
else => .s64,
|
else => .s64,
|
||||||
};
|
};
|
||||||
@@ -9376,15 +9647,17 @@ pub const Lowering = struct {
|
|||||||
defer tmp_bindings.deinit();
|
defer tmp_bindings.deinit();
|
||||||
|
|
||||||
for (fd.type_params) |tp| {
|
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;
|
var found = false;
|
||||||
for (fd.params, 0..) |param, pi| {
|
for (fd.params, 0..) |param, pi| {
|
||||||
if (std.mem.eql(u8, param.name, tp.name)) {
|
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]);
|
const ty = self.resolveTypeArg(c.args[pi]);
|
||||||
tmp_bindings.put(tp.name, ty) catch {};
|
tmp_bindings.put(tp.name, ty) catch {};
|
||||||
|
found = true;
|
||||||
}
|
}
|
||||||
found = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9482,6 +9755,21 @@ pub const Lowering = struct {
|
|||||||
return self.buildProtocolErasure(operand, operand_node, src_ty, dst_ty);
|
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);
|
const result = self.coerceToType(operand, src_ty, dst_ty);
|
||||||
|
|
||||||
// User-space fallback via `impl Into(Target) for Source`. Only fires
|
// 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 {
|
fn emitError(self: *Lowering, name: []const u8, span: ?ast.Span) Ref {
|
||||||
if (self.diagnostics) |diags| {
|
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);
|
return self.emitPlaceholder(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -516,6 +516,7 @@ fn writeConstant(val: ConstantValue, writer: Writer) !void {
|
|||||||
.zeroinit => try writer.writeAll("zeroinit"),
|
.zeroinit => try writer.writeAll("zeroinit"),
|
||||||
.aggregate => try writer.writeAll("{...}"),
|
.aggregate => try writer.writeAll("{...}"),
|
||||||
.vtable => try writer.writeAll("vtable{...}"),
|
.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),
|
.function_type_expr => |ft| resolveFunctionType(&ft, table),
|
||||||
.closure_type_expr => |ct| resolveClosureType(&ct, table),
|
.closure_type_expr => |ct| resolveClosureType(&ct, table),
|
||||||
.tuple_type_expr => |tt| resolveTupleType(&tt, table),
|
.tuple_type_expr => |tt| resolveTupleType(&tt, table),
|
||||||
|
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table),
|
||||||
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table),
|
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table),
|
||||||
.inferred_type => .s64, // inferred — default until we have type inference
|
.inferred_type => .s64, // inferred — default until we have type inference
|
||||||
// Inline type declarations (used as field types)
|
// 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 {
|
fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTable) TypeId {
|
||||||
// Strip module prefix (e.g. "std.Vector" → "Vector")
|
// 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;
|
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_count", .detail = "($T: Type) -> s32" },
|
||||||
.{ .label = "field_name", .detail = "($T: Type, idx: s32) -> string" },
|
.{ .label = "field_name", .detail = "($T: Type, idx: s32) -> string" },
|
||||||
.{ .label = "field_value", .detail = "(s: $T, idx: s32) -> Any" },
|
.{ .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 = "cast", .detail = "(Type) expr — prefix type cast" },
|
||||||
.{ .label = "malloc", .detail = "(size: s64) -> *void" },
|
.{ .label = "malloc", .detail = "(size: s64) -> *void" },
|
||||||
.{ .label = "free", .detail = "(ptr: *void) -> 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_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_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 = "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 = "cast", .label = "cast(Type) expr", .params = &.{"Type"} },
|
||||||
.{ .name = "malloc", .label = "malloc(size: s64) -> *void", .params = &.{"size: s64"} },
|
.{ .name = "malloc", .label = "malloc(size: s64) -> *void", .params = &.{"size: s64"} },
|
||||||
.{ .name = "free", .label = "free(ptr: *void) -> void", .params = &.{"ptr: *void"} },
|
.{ .name = "free", .label = "free(ptr: *void) -> void", .params = &.{"ptr: *void"} },
|
||||||
|
|||||||
@@ -2240,6 +2240,10 @@ pub const Parser = struct {
|
|||||||
if (self.isLambda()) {
|
if (self.isLambda()) {
|
||||||
return self.parseLambda();
|
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 '('
|
self.advance(); // skip '('
|
||||||
|
|
||||||
// Check for named tuple: (name: expr, ...)
|
// Check for named tuple: (name: expr, ...)
|
||||||
@@ -2312,8 +2316,7 @@ pub const Parser = struct {
|
|||||||
null;
|
null;
|
||||||
return try self.createNode(start, .{ .return_stmt = .{ .value = value } });
|
return try self.createNode(start, .{ .return_stmt = .{ .value = value } });
|
||||||
},
|
},
|
||||||
.l_bracket => {
|
.l_bracket, .star, .question => {
|
||||||
// Type expression in expression position: []T.[...] or [N]T.[...]
|
|
||||||
return try self.parseTypeExpr();
|
return try self.parseTypeExpr();
|
||||||
},
|
},
|
||||||
.l_brace => {
|
.l_brace => {
|
||||||
@@ -2728,6 +2731,32 @@ pub const Parser = struct {
|
|||||||
return self.current.tag;
|
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 {
|
fn isLambda(self: *Parser) bool {
|
||||||
const saved_lexer = self.lexer;
|
const saved_lexer = self.lexer;
|
||||||
const saved_current = self.current;
|
const saved_current = self.current;
|
||||||
@@ -2845,7 +2874,55 @@ pub const Parser = struct {
|
|||||||
// ends with `;` directly after the param list — recognise it as a
|
// ends with `;` directly after the param list — recognise it as a
|
||||||
// function def (not a constant) so it goes through parseFnDecl.
|
// function def (not a constant) so it goes through parseFnDecl.
|
||||||
if (self.struct_default_compiler and tag == .semicolon) return true;
|
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 {
|
fn parseOptionalCallConv(self: *Parser) anyerror!ast.CallingConvention {
|
||||||
|
|||||||
@@ -655,7 +655,7 @@ pub const Analyzer = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Built-in names that aren't declared in source
|
// 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| {
|
for (builtins) |b| {
|
||||||
if (std.mem.eql(u8, name, b)) return;
|
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}
|
a 1 : Foo{a: 1, b: 42, c: 8, d: 17}
|
||||||
b: Foo{a: 1, b: 1, c: 101, d: 1}
|
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}
|
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
|
4
|
||||||
16
|
16
|
||||||
8
|
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-s32: 4
|
||||||
sizeof-f64: 8
|
sizeof-f64: 8
|
||||||
sizeof-struct: 8
|
sizeof-struct: 8
|
||||||
|
alignof-u8: 1
|
||||||
|
alignof-s32: 4
|
||||||
|
alignof-s64: 8
|
||||||
|
alignof-struct: 4
|
||||||
typeof: int
|
typeof: int
|
||||||
typeof-float: float
|
typeof-float: float
|
||||||
typeof-string: string
|
typeof-string: string
|
||||||
@@ -385,12 +389,12 @@ bytes len: 3
|
|||||||
--- allocators ---
|
--- allocators ---
|
||||||
gpa allocs: 2
|
gpa allocs: 2
|
||||||
gpa final: 0
|
gpa final: 0
|
||||||
arena chunks: 1
|
arena chunks: 2
|
||||||
arena overflow: 2
|
arena overflow: 3
|
||||||
arena a1: 42
|
arena a1: 42
|
||||||
arena a3: 99
|
arena a3: 99
|
||||||
arena reset idx: 0
|
arena reset idx: 0
|
||||||
arena reset gpa: 1
|
arena reset gpa: 2
|
||||||
arena deinit: 0
|
arena deinit: 0
|
||||||
buf pos: 48
|
buf pos: 48
|
||||||
buf overflow: 0
|
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
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@str.1 = private unnamed_addr constant [4 x i8] c"()V\00", align 1
|
||||||
@SX_JNI_CLS_noop____V = internal global ptr null
|
@SX_JNI_CLS_noop____V = internal global ptr null
|
||||||
@@ -15,14 +16,38 @@ declare void @out(ptr) #0
|
|||||||
|
|
||||||
declare ptr @malloc(i64)
|
declare ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -37,8 +62,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -62,7 +87,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -77,7 +102,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -88,6 +113,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -156,7 +196,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -164,7 +204,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -189,7 +229,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -310,6 +350,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@str.1 = private unnamed_addr constant [4 x i8] c"()I\00", align 1
|
||||||
@SX_JNI_CLS_getCount____I = internal global ptr null
|
@SX_JNI_CLS_getCount____I = internal global ptr null
|
||||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
|||||||
|
|
||||||
declare ptr @malloc(i64)
|
declare ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@str.1 = private unnamed_addr constant [4 x i8] c"()J\00", align 1
|
||||||
@SX_JNI_CLS_currentTimeMillis____J = internal global ptr null
|
@SX_JNI_CLS_currentTimeMillis____J = internal global ptr null
|
||||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
|||||||
|
|
||||||
declare ptr @malloc(i64)
|
declare ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@str.1 = private unnamed_addr constant [4 x i8] c"()D\00", align 1
|
||||||
@SX_JNI_CLS_getValue____D = internal global ptr null
|
@SX_JNI_CLS_getValue____D = internal global ptr null
|
||||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
|||||||
|
|
||||||
declare ptr @malloc(i64)
|
declare ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@str.1 = private unnamed_addr constant [4 x i8] c"()Z\00", align 1
|
||||||
@SX_JNI_CLS_isShown____Z = internal global ptr null
|
@SX_JNI_CLS_isShown____Z = internal global ptr null
|
||||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
|||||||
|
|
||||||
declare ptr @malloc(i64)
|
declare ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@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
|
@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 ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@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
|
@SX_JNI_CLS_max___II_I = internal global ptr null
|
||||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
|||||||
|
|
||||||
declare ptr @malloc(i64)
|
declare ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -282,6 +322,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@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
|
@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 ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -285,6 +325,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
@context = internal global { { ptr, ptr, ptr }, ptr } zeroinitializer
|
||||||
@g_should_call = internal global i1 false
|
@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 = 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
|
@str.1 = private unnamed_addr constant [4 x i8] c"()V\00", align 1
|
||||||
@SX_JNI_CLS_noop____V = internal global ptr null
|
@SX_JNI_CLS_noop____V = internal global ptr null
|
||||||
@@ -13,14 +14,38 @@ declare void @out(ptr) #0
|
|||||||
|
|
||||||
declare ptr @malloc(i64)
|
declare ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -35,8 +60,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -60,7 +85,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -75,7 +100,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -86,6 +111,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -154,7 +194,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -162,7 +202,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -187,7 +227,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -283,6 +323,20 @@ if.merge.1: ; preds = %if.then.0, %entry
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@OS = internal global i64 0
|
@OS = internal global i64 0
|
||||||
@ARCH = internal global i64 0
|
@ARCH = internal global i64 0
|
||||||
@POINTER_SIZE = internal global i64 8
|
@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_init = internal global ptr null
|
||||||
@OBJC_SELECTOR_REFERENCES_release = 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
|
@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 ptr @malloc(i64)
|
||||||
|
|
||||||
|
declare void @free(ptr)
|
||||||
|
|
||||||
declare ptr @memcpy(ptr, ptr, i64)
|
declare ptr @memcpy(ptr, ptr, i64)
|
||||||
|
|
||||||
declare ptr @memset(ptr, i32, 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
|
; 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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @GPA.alloc(ptr %0, i64 %1) #0 {
|
||||||
@@ -38,8 +63,8 @@ entry:
|
|||||||
%add = add i64 %loadN, 1
|
%add = add i64 %loadN, 1
|
||||||
store i64 %add, ptr %gep, align 8
|
store i64 %add, ptr %gep, align 8
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%loadN = load i64, ptr %allocaN, align 8
|
||||||
%malloc = call ptr @malloc(i64 %loadN)
|
%call = call ptr @malloc(i64 %loadN)
|
||||||
ret ptr %malloc
|
ret ptr %call
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
@@ -63,7 +88,7 @@ entry:
|
|||||||
declare void @Arena.add_chunk(ptr, i64) #0
|
declare void @Arena.add_chunk(ptr, i64) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @Arena.reset(ptr) #0
|
declare void @Arena.reset(ptr) #0
|
||||||
@@ -78,7 +103,7 @@ declare ptr @Arena.alloc(ptr, i64) #0
|
|||||||
declare void @Arena.dealloc(ptr, ptr) #0
|
declare void @Arena.dealloc(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; 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
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.reset(ptr) #0
|
declare void @BufAlloc.reset(ptr) #0
|
||||||
@@ -89,6 +114,21 @@ declare ptr @BufAlloc.alloc(ptr, i64) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @BufAlloc.dealloc(ptr, ptr) #0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
define internal { ptr, i64 } @cstring(i64 %0) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -157,7 +197,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
%loadN = load { ptr, i64 }, ptr %alloca, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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 i64, ptr %allocaN, align 8
|
||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
@@ -165,7 +205,7 @@ entry:
|
|||||||
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
%dptrN = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -190,7 +230,7 @@ entry:
|
|||||||
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
%igp.data = extractvalue { ptr, i64 } %loadN, 0
|
||||||
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
%igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN
|
||||||
%loadN = load i64, ptr %allocaN, align 8
|
%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
|
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
|
||||||
ret { ptr, i64 } %loadN
|
ret { ptr, i64 } %loadN
|
||||||
}
|
}
|
||||||
@@ -359,6 +399,20 @@ entry:
|
|||||||
ret i32 0
|
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
|
; Function Attrs: nounwind
|
||||||
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
define internal ptr @__thunk_GPA_Allocator_alloc(ptr %0, i64 %1) #0 {
|
||||||
entry:
|
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