// Phase 1.1 โ€” the compiler-internal heap-copy that backs `xx ` // protocol erasure must dispatch through `context.allocator`, not call // libc malloc directly. So when a `push Context.{ allocator = tracer }` // block is active, a `xx StructLiteral.{}` inside it MUST be allocated // by the tracker. // // Note: `xx` only heap-copies for RVALUES (struct literals, call results). // `xx ` (an identifier, field access, index, or deref) borrows // the operand's storage, so it never allocates and never reaches this // path. See specs.md ยง3 โ€” Protocol value ownership and lifetime. #import "modules/std.sx"; #import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it. Tracer :: struct { count: i64; init :: () -> *Tracer { t : *Tracer = xx libc_malloc(size_of(Tracer)); t.count = 0; t } } impl Allocator for Tracer { alloc_bytes :: (self: *Tracer, size: i64) -> *void { self.count += 1; return libc_malloc(size); } dealloc_bytes :: (self: *Tracer, ptr: *void) { libc_free(ptr); } } ByValue :: struct { x: i64; y: i64; } main :: () -> i32 { tracer := Tracer.init(); push Context.{ allocator = xx tracer, data = null } { // Struct-literal operand: rvalue โ†’ heap-copy through context.allocator. ignore : Allocator = xx ByValue.{ x = 1, y = 2 }; _ = ignore; } print("Tracer.count = {}\n", tracer.count); 0 }