From d6078c2e6bcdc996107356a47239fae25d209292 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 08:53:51 +0300 Subject: [PATCH 01/26] test(backend): lock LLVM type/ABI shapes before A7.1 extraction (A7.1 scaffolding step 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test-first scaffolding for LLVM backend modularization (Phase A7.1) before the type/ABI helpers move into src/backend/llvm/{types,abi}.zig. Visibility-only change to the targets — no behavior change. Closes the ARCH-SAFETY "no generic ABI snapshot" gap. - 2 new emit_llvm.test.zig tests: - abiCoerceParamType across every C-ABI size bucket: <=8 -> i64, 9-16 -> [2 x i64], >16 -> ptr, HFA (all-float/all-double, <=4 fields) -> unchanged, string -> ptr, slice -> ptr, scalar -> unchanged. Built via a local internStruct helper (field slice in the module arena -> no testing-allocator leak); asserts against emitter.cached_* + LLVMArrayType2. - needsByval: true only for >16-byte non-HFA struct; false for <=16 / HFA / string / slice / non-struct. - 1 new .ir snapshot: 1202-ffi-cc-c-large-aggregate (the canonical callconv(.c) >16-byte byval example that directly documents abiCoerceParamType) — pins the byval param path end-to-end (5 byval + entry reload + 2 sret from Arena.init). Path-free + idempotent (verified across two captures). Suite count unchanged (snapshot added to an existing example). - Widened abiCoerceParamType + needsByval to pub (visibility only; abiCoerceParamTypeEx/materializeByvalArg/verifySizes stay private — move with callers in sub-step 2). No logic touched. - Recorded the A7.1 coverage inventory + residual gaps (wasm32 usize->i32 branch, fn-ptr large-aggregate 1203/1204) in ARCH-SAFETY.md. Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (no churn beyond the new 1202 .ir). --- .../expected/1202-ffi-cc-c-large-aggregate.ir | 182 ++++++++++++++++++ src/ir/emit_llvm.test.zig | 77 ++++++++ src/ir/emit_llvm.zig | 4 +- 3 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 examples/expected/1202-ffi-cc-c-large-aggregate.ir diff --git a/examples/expected/1202-ffi-cc-c-large-aggregate.ir b/examples/expected/1202-ffi-cc-c-large-aggregate.ir new file mode 100644 index 0000000..1f60847 --- /dev/null +++ b/examples/expected/1202-ffi-cc-c-large-aggregate.ir @@ -0,0 +1,182 @@ + +@__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 } + +; Function Attrs: nounwind +declare void @out(ptr) #0 + +declare ptr @malloc(i64) + +declare void @free(ptr) + +declare ptr @memcpy(ptr, ptr, i64) + +declare ptr @memset(ptr, i32, i64) + +; Function Attrs: nounwind +define internal ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %2, ptr %allocaN, align 8 + %load = load i64, ptr %allocaN, align 8 + %call = call ptr @malloc(i64 %load) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca ptr, align 8 + store ptr %2, ptr %allocaN, align 8 + %load = load ptr, ptr %allocaN, align 8 + call void @free(ptr %load) + ret void +} + +; Function Attrs: nounwind +declare i64 @GPA.init(ptr) #0 + +; Function Attrs: nounwind +declare ptr @GPA.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @GPA.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.add_chunk(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.init(ptr sret({ ptr, i64, { ptr, ptr, ptr } }), ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.deinit(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @Arena.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.init(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.init(ptr sret({ { ptr, ptr, ptr }, i64, i64, i64 }), ptr, ptr) #0 + +; Function Attrs: nounwind +declare i64 @TrackingAllocator.leak_count(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.report(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @TrackingAllocator.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @cstring(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @int_to_string(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @bool_to_string(ptr, i1) #0 + +; Function Attrs: nounwind +declare ptr @float_to_string(ptr, double) #0 + +; Function Attrs: nounwind +declare void @hex_group(ptr, ptr, i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @int_to_hex_string(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @concat(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @substr(ptr, ptr, i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @xml_escape(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @path_join(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @any_to_string(ptr, [2 x i64]) #0 + +; Function Attrs: nounwind +declare ptr @build_format(ptr, ptr) #0 + +; Function Attrs: nounwind +define internal i64 @accept_c(ptr %0) #0 { +entry: + %byval.load = load { i64, i64, i64, i64 }, ptr %0, align 8 + %alloca = alloca { i64, i64, i64, i64 }, align 8 + store { i64, i64, i64, i64 } %byval.load, ptr %alloca, align 8 + %load = load { i64, i64, i64, i64 }, ptr %alloca, align 8 + %sg = extractvalue { i64, i64, i64, i64 } %load, 0 + %loadN = load { i64, i64, i64, i64 }, ptr %alloca, align 8 + %sgN = extractvalue { i64, i64, i64, i64 } %loadN, 1 + %add = add i64 %sg, %sgN + %loadN = load { i64, i64, i64, i64 }, ptr %alloca, align 8 + %sgN = extractvalue { i64, i64, i64, i64 } %loadN, 2 + %addN = add i64 %add, %sgN + %loadN = load { i64, i64, i64, i64 }, ptr %alloca, align 8 + %sgN = extractvalue { i64, i64, i64, i64 } %loadN, 3 + %addN = add i64 %addN, %sgN + ret i64 %addN +} + +; Function Attrs: nounwind +define i32 @main() #0 { +entry: + %alloca = alloca { i64, i64, i64, i64 }, align 8 + store { i64, i64, i64, i64 } { i64 1, i64 10, i64 100, i64 1000 }, ptr %alloca, align 8 + %load = load { i64, i64, i64, i64 }, ptr %alloca, align 8 + %byval.tmp = alloca { i64, i64, i64, i64 }, align 8 + store { i64, i64, i64, i64 } %load, ptr %byval.tmp, align 8 + %call = call i64 @accept_c(ptr %byval.tmp) + %icmp = icmp ne i64 %call, 1111 + br i1 %icmp, label %if.then.0, label %if.merge.1 + +if.then.0: ; preds = %entry + ret i32 1 + +if.merge.1: ; preds = %entry + ret i32 0 +} + +; Function Attrs: nounwind +define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %call = call ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + call void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) + ret void +} + + diff --git a/src/ir/emit_llvm.test.zig b/src/ir/emit_llvm.test.zig index 2406f53..8d9426f 100644 --- a/src/ir/emit_llvm.test.zig +++ b/src/ir/emit_llvm.test.zig @@ -353,6 +353,83 @@ test "emit: type conversion toLLVMType" { _ = emitter.toLLVMType(.noreturn); } +// ── A7.1 scaffolding: ABI param coercion ──────────────────────────── +// Lock the C-ABI struct-coercion buckets (abiCoerceParamType / needsByval), +// which feed callconv(.c) / #foreign signatures, before they move to +// src/backend/llvm/abi.zig in A7.1 sub-step 2. + +const llvm = @import("../llvm_api.zig"); +const cc = llvm.c; + +fn internStruct(module: *Module, name: []const u8, field_tys: []const TypeId) TypeId { + var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty; + defer fields.deinit(std.testing.allocator); + for (field_tys, 0..) |fty, i| { + var nb: [8]u8 = undefined; + const fname = std.fmt.bufPrint(&nb, "f{d}", .{i}) catch unreachable; + fields.append(std.testing.allocator, .{ .name = str(module, fname), .ty = fty }) catch unreachable; + } + // Dupe into the module arena so the interned struct's field slice lives for + // the module's lifetime (freed at module.deinit) — no testing-allocator leak. + const owned = module.slice_arena.allocator().dupe(types.TypeInfo.StructInfo.Field, fields.items) catch unreachable; + return module.types.intern(.{ .@"struct" = .{ .name = str(module, name), .fields = owned } }); +} + +test "emit: abiCoerceParamType coerces C-ABI structs by size bucket" { + const alloc = std.testing.allocator; + var module = Module.init(alloc); + defer module.deinit(); + + // Intern the shapes before building the emitter (toLLVMType reads live). + const small = internStruct(&module, "Small", &.{ .s32, .s32 }); // 8 bytes + const mid = internStruct(&module, "Mid", &.{ .s64, .s64 }); // 16 bytes + const big = internStruct(&module, "Big", &.{ .s64, .s64, .s64 }); // 24 bytes + const hfa_f = internStruct(&module, "HfaF", &.{ .f32, .f32, .f32, .f32 }); // 16, all-float + const hfa_d = internStruct(&module, "HfaD", &.{ .f64, .f64 }); // 16, all-double + const sl = module.types.sliceOf(.s32); + + var emitter = LLVMEmitter.init(alloc, &module, "test_abi", .{}); + defer emitter.deinit(); + + // ≤ 8 bytes → i64. + try std.testing.expect(emitter.abiCoerceParamType(small, emitter.toLLVMType(small)) == emitter.cached_i64); + // 9–16 bytes → [2 x i64]. + try std.testing.expect(emitter.abiCoerceParamType(mid, emitter.toLLVMType(mid)) == cc.LLVMArrayType2(emitter.cached_i64, 2)); + // > 16 bytes → ptr (passed byval at the call/sig sites). + try std.testing.expect(emitter.abiCoerceParamType(big, emitter.toLLVMType(big)) == emitter.cached_ptr); + // HFA (all-float / all-double, ≤ 4 fields) → unchanged. + try std.testing.expect(emitter.abiCoerceParamType(hfa_f, emitter.toLLVMType(hfa_f)) == emitter.toLLVMType(hfa_f)); + try std.testing.expect(emitter.abiCoerceParamType(hfa_d, emitter.toLLVMType(hfa_d)) == emitter.toLLVMType(hfa_d)); + // string / slice collapse to ptr at the C-API boundary (len dropped). + try std.testing.expect(emitter.abiCoerceParamType(.string, emitter.toLLVMType(.string)) == emitter.cached_ptr); + try std.testing.expect(emitter.abiCoerceParamType(sl, emitter.toLLVMType(sl)) == emitter.cached_ptr); + // Scalars pass through unchanged. + try std.testing.expect(emitter.abiCoerceParamType(.s32, emitter.toLLVMType(.s32)) == emitter.toLLVMType(.s32)); +} + +test "emit: needsByval only for > 16-byte non-HFA structs" { + const alloc = std.testing.allocator; + var module = Module.init(alloc); + defer module.deinit(); + + const small = internStruct(&module, "Small", &.{ .s32, .s32 }); + const mid = internStruct(&module, "Mid", &.{ .s64, .s64 }); + const big = internStruct(&module, "Big", &.{ .s64, .s64, .s64 }); + const hfa_d = internStruct(&module, "HfaD", &.{ .f64, .f64 }); + const sl = module.types.sliceOf(.s32); + + var emitter = LLVMEmitter.init(alloc, &module, "test_byval", .{}); + defer emitter.deinit(); + + try std.testing.expect(emitter.needsByval(big, emitter.toLLVMType(big))); // > 16 + try std.testing.expect(!emitter.needsByval(small, emitter.toLLVMType(small))); + try std.testing.expect(!emitter.needsByval(mid, emitter.toLLVMType(mid))); // exactly 16 + try std.testing.expect(!emitter.needsByval(hfa_d, emitter.toLLVMType(hfa_d))); // HFA + try std.testing.expect(!emitter.needsByval(.string, emitter.toLLVMType(.string))); + try std.testing.expect(!emitter.needsByval(sl, emitter.toLLVMType(sl))); + try std.testing.expect(!emitter.needsByval(.s32, emitter.toLLVMType(.s32))); // non-struct +} + // ── Struct/Enum/Union tests ───────────────────────────────────────── test "emit: struct_init and struct_get" { diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 45c8be5..a0c2deb 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -4718,7 +4718,7 @@ pub const LLVMEmitter = struct { // - Small integer struct (≤ 8 bytes, non-HFA) → i64 // - HFA (homogeneous float aggregate) → leave as-is (LLVM handles it) - fn abiCoerceParamType(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef { + pub fn abiCoerceParamType(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef { return self.abiCoerceParamTypeEx(ir_ty, llvm_ty, true); } @@ -4787,7 +4787,7 @@ pub const LLVMEmitter = struct { return self.cached_ptr; } - fn needsByval(self: *LLVMEmitter, ir_ty: TypeId, raw_llvm_ty: c.LLVMTypeRef) bool { + pub fn needsByval(self: *LLVMEmitter, ir_ty: TypeId, raw_llvm_ty: c.LLVMTypeRef) bool { if (self.target_config.isWasm32()) return false; if (ir_ty == .string) return false; if (!ir_ty.isBuiltin()) { From e50caa4628961f461373c827430f45b1ca810fc2 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 08:59:26 +0300 Subject: [PATCH 02/26] test(backend): trim trailing blank lines in 1202 .ir snapshot (A7.1 scaffolding fix) Codex review of d6078c2 flagged a blank line at EOF in the new examples/expected/1202-ffi-cc-c-large-aggregate.ir. Collapse the trailing newlines to a single one so `git diff --check` is clean. Test-safe: the runner reads both expected and actual IR through $(...) command substitution, which strips trailing newlines, so the comparison is unaffected (1202 still ok). Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0. --- examples/expected/1202-ffi-cc-c-large-aggregate.ir | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/expected/1202-ffi-cc-c-large-aggregate.ir b/examples/expected/1202-ffi-cc-c-large-aggregate.ir index 1f60847..c9fe71e 100644 --- a/examples/expected/1202-ffi-cc-c-large-aggregate.ir +++ b/examples/expected/1202-ffi-cc-c-large-aggregate.ir @@ -178,5 +178,3 @@ entry: call void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) ret void } - - From 71f1cb2fb0992bb9845d24a17d3a9b5d8a452555 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 09:10:27 +0300 Subject: [PATCH 03/26] refactor(backend): extract LLVM type/ABI lowering into src/backend/llvm/ (A7.1 step 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the LLVM type-mapping and C-ABI coercion helpers out of emit_llvm.zig into the first src/backend/llvm/ modules. Behavior-preserving relocation — the only rewrites are module plumbing and self.* -> self.e.* facade access. - src/backend/llvm/types.zig (TypeLowering): toLLVMType + toLLVMTypeInfo. - src/backend/llvm/abi.zig (AbiLowering): abiCoerceParamType / abiCoerceParamTypeEx / needsByval / materializeByvalArg. - Both are backend *LLVMEmitter facades (field `e`) — the backend analogue of the IR-side *Lowering facades, NOT a *Lowering facade. They reach the cached LLVM handles, IR type table, module data layout, builder, and the memoizing composite-type getters via self.e.*. - LLVMEmitter stays the facade: toLLVMType (~97 callers) + abiCoerceParamType / abiCoerceParamTypeEx / needsByval / materializeByvalArg kept as thin wrappers delegating through new typeLowering()/abiLowering() accessors. Zero caller churn. toLLVMTypeInfo deleted (sole caller moved). - Widened getStringStructType / getAnyStructType / getClosureStructType to pub (the moved toLLVMTypeInfo calls them back; their memoization stays on LLVMEmitter). verifySizes stays in emit_llvm.zig (size-assertion pass, not type/ ABI lowering). No ABI/type logic, branch order, diagnostic text, or snapshot changed. Circular import (emit_llvm <-> backend/llvm) resolves via the pointer facade. Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (1202 .ir + the 2 ABI unit tests unchanged, no churn). --- src/backend/llvm/abi.zig | 123 +++++++++++++++++ src/backend/llvm/types.zig | 178 ++++++++++++++++++++++++ src/ir/emit_llvm.zig | 272 +++---------------------------------- 3 files changed, 322 insertions(+), 251 deletions(-) create mode 100644 src/backend/llvm/abi.zig create mode 100644 src/backend/llvm/types.zig diff --git a/src/backend/llvm/abi.zig b/src/backend/llvm/abi.zig new file mode 100644 index 0000000..e02ca48 --- /dev/null +++ b/src/backend/llvm/abi.zig @@ -0,0 +1,123 @@ +const std = @import("std"); +const llvm = @import("../../llvm_api.zig"); +const c = llvm.c; +const ir_types = @import("../../ir/types.zig"); +const emit = @import("../../ir/emit_llvm.zig"); + +const TypeId = ir_types.TypeId; +const LLVMEmitter = emit.LLVMEmitter; + +/// C-ABI parameter coercion (architecture phase A7.1), extracted from +/// `LLVMEmitter`. A backend `*LLVMEmitter` facade: it borrows the emitter for +/// the cached LLVM handles, the IR type table, the module data layout, and the +/// IR builder. `LLVMEmitter.{abiCoerceParamType, abiCoerceParamTypeEx, +/// needsByval, materializeByvalArg}` are thin wrappers delegating here. +/// +/// On ARM64 (and x86_64), the C calling convention coerces small struct +/// arguments to integers for register passing: +/// - String/slice {ptr, i64} → ptr (extract raw pointer) +/// - Small integer struct (≤ 8 bytes, non-HFA) → i64 +/// - HFA (homogeneous float aggregate) → leave as-is (LLVM handles it) +pub const AbiLowering = struct { + e: *LLVMEmitter, + + pub fn abiCoerceParamType(self: AbiLowering, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef { + return self.abiCoerceParamTypeEx(ir_ty, llvm_ty, true); + } + + /// Same as `abiCoerceParamType` but with an explicit + /// `is_foreign_c_api` knob. When true, sx `string` / `[]T` slices + /// collapse to `ptr` — the libc convention where the user writes + /// `string` to mean `char *` and the length is dropped. When + /// false (sx-internal `callconv(.c)` like block trampolines), the + /// full slice shape is preserved and goes through the general + /// struct-coerce path (16-byte slice → `[2 x i64]`, lands in two + /// registers on AArch64 — the true C ABI for a 16-byte + /// aggregate). Without the split, sx-to-sx calls through a + /// `(*Block, string) -> void callconv(.c)` fn-pointer mismatched + /// the caller's `{ptr, i64}` value against the trampoline's + /// collapsed `ptr` param. + pub fn abiCoerceParamTypeEx(self: AbiLowering, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef, is_foreign_c_api: bool) c.LLVMTypeRef { + if (is_foreign_c_api) { + if (ir_ty == .string) return self.e.cached_ptr; + if (!ir_ty.isBuiltin()) { + const info = self.e.ir_mod.types.get(ir_ty); + if (info == .slice) return self.e.cached_ptr; + } + } + + // WASM32: usize/isize are pointer-sized (i32 on wasm32). + // Other integer types (s64, u64) keep their declared size — they represent + // genuinely 64-bit values (SDL_WindowFlags, timestamps, etc.). + if (self.e.target_config.isWasm32()) { + if (ir_ty == .usize or ir_ty == .isize) return self.e.cached_i32; + return llvm_ty; + } + + // Only coerce struct types + if (c.LLVMGetTypeKind(llvm_ty) != c.LLVMStructTypeKind) return llvm_ty; + + // Check if it's an HFA (all float or all double fields) — leave as-is + const n_fields = c.LLVMCountStructElementTypes(llvm_ty); + if (n_fields >= 1 and n_fields <= 4) { + var all_float = true; + var all_double = true; + var fi: c_uint = 0; + while (fi < n_fields) : (fi += 1) { + const ft = c.LLVMStructGetTypeAtIndex(llvm_ty, fi); + const fk = c.LLVMGetTypeKind(ft); + if (fk != c.LLVMFloatTypeKind) all_float = false; + if (fk != c.LLVMDoubleTypeKind) all_double = false; + } + if (all_float or all_double) return llvm_ty; + } + + // Small struct (≤ 8 bytes) → coerce to i64 + const size = c.LLVMABISizeOfType( + c.LLVMGetModuleDataLayout(self.e.llvm_module), + llvm_ty, + ); + if (size <= 8) return self.e.cached_i64; + + // Medium struct (9-16 bytes) → coerce to [2 x i64] + if (size <= 16) { + return c.LLVMArrayType2(self.e.cached_i64, 2); + } + + // Large composite (> 16 bytes) → pass by reference: ptr + byval() at + // the call/sig sites. LLVM's AArch64/x86_64 backend lowers byval to + // the right ABI sequence (caller copy + indirect arg). + return self.e.cached_ptr; + } + + pub fn needsByval(self: AbiLowering, ir_ty: TypeId, raw_llvm_ty: c.LLVMTypeRef) bool { + if (self.e.target_config.isWasm32()) return false; + if (ir_ty == .string) return false; + if (!ir_ty.isBuiltin()) { + const info = self.e.ir_mod.types.get(ir_ty); + if (info == .slice) return false; + } + if (c.LLVMGetTypeKind(raw_llvm_ty) != c.LLVMStructTypeKind) return false; + const n = c.LLVMCountStructElementTypes(raw_llvm_ty); + if (n >= 1 and n <= 4) { + var all_f = true; + var all_d = true; + var i: c_uint = 0; + while (i < n) : (i += 1) { + const ft = c.LLVMStructGetTypeAtIndex(raw_llvm_ty, i); + const fk = c.LLVMGetTypeKind(ft); + if (fk != c.LLVMFloatTypeKind) all_f = false; + if (fk != c.LLVMDoubleTypeKind) all_d = false; + } + if (all_f or all_d) return false; + } + const size = c.LLVMABISizeOfType(c.LLVMGetModuleDataLayout(self.e.llvm_module), raw_llvm_ty); + return size > 16; + } + + pub fn materializeByvalArg(self: AbiLowering, val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef) c.LLVMValueRef { + const tmp = c.LLVMBuildAlloca(self.e.builder, struct_ty, "byval.tmp"); + _ = c.LLVMBuildStore(self.e.builder, val, tmp); + return tmp; + } +}; diff --git a/src/backend/llvm/types.zig b/src/backend/llvm/types.zig new file mode 100644 index 0000000..aa7c54c --- /dev/null +++ b/src/backend/llvm/types.zig @@ -0,0 +1,178 @@ +const std = @import("std"); +const llvm = @import("../../llvm_api.zig"); +const c = llvm.c; +const ir_types = @import("../../ir/types.zig"); +const emit = @import("../../ir/emit_llvm.zig"); + +const TypeId = ir_types.TypeId; +const LLVMEmitter = emit.LLVMEmitter; + +/// IR-type → LLVM-type lowering (architecture phase A7.1), extracted from +/// `LLVMEmitter`. A backend `*LLVMEmitter` facade (the backend analogue of the +/// IR-side `*Lowering` facades): it borrows the emitter for the cached LLVM +/// handles (`context`/`cached_*`), the IR type table (`ir_mod`), the scratch +/// allocator, and the memoizing composite-type getters +/// (`getStringStructType`/`getAnyStructType`/`getClosureStructType`) that stay +/// on `LLVMEmitter`. `LLVMEmitter.toLLVMType` is a thin wrapper delegating here. +pub const TypeLowering = struct { + e: *LLVMEmitter, + + pub fn toLLVMType(self: TypeLowering, ty: TypeId) c.LLVMTypeRef { + return switch (ty) { + .void => self.e.cached_void, + .bool => self.e.cached_i1, + .s8 => self.e.cached_i8, + .s16 => self.e.cached_i16, + .s32 => self.e.cached_i32, + .s64 => self.e.cached_i64, + .u8 => self.e.cached_i8, + .u16 => self.e.cached_i16, + .u32 => self.e.cached_i32, + .u64 => self.e.cached_i64, + .f32 => self.e.cached_f32, + .f64 => self.e.cached_f64, + .string => self.e.getStringStructType(), + .any => self.e.getAnyStructType(), + .noreturn => self.e.cached_void, + .isize, .usize => if (self.e.target_config.isWasm32()) self.e.cached_i32 else self.e.cached_i64, + else => self.toLLVMTypeInfo(ty), + }; + } + + fn toLLVMTypeInfo(self: TypeLowering, ty: TypeId) c.LLVMTypeRef { + const info = self.e.ir_mod.types.get(ty); + return switch (info) { + .signed => |w| switch (w) { + 1 => self.e.cached_i1, + 8 => self.e.cached_i8, + 16 => self.e.cached_i16, + 32 => self.e.cached_i32, + 64 => self.e.cached_i64, + else => c.LLVMIntTypeInContext(self.e.context, w), + }, + .unsigned => |w| switch (w) { + 1 => self.e.cached_i1, + 8 => self.e.cached_i8, + 16 => self.e.cached_i16, + 32 => self.e.cached_i32, + 64 => self.e.cached_i64, + else => c.LLVMIntTypeInContext(self.e.context, w), + }, + .f32 => self.e.cached_f32, + .f64 => self.e.cached_f64, + .void => self.e.cached_void, + .bool => self.e.cached_i1, + .error_set => self.e.cached_i32, // u32 tag id on the error channel + .string => self.e.getStringStructType(), + .pointer, .many_pointer, .function => self.e.cached_ptr, + .closure => self.e.getClosureStructType(), + .slice => self.e.getStringStructType(), // same {ptr, i64} layout + .optional => |opt| { + // ?*T / ?fn → bare pointer (null = none) + const child_info = self.e.ir_mod.types.get(opt.child); + if (child_info == .pointer or child_info == .many_pointer or child_info == .function) { + return self.e.cached_ptr; + } + if (child_info == .closure) { + return self.e.getClosureStructType(); + } + // ?Protocol → protocol struct (ctx ptr = field 0 is null when none). + if (child_info == .@"struct" and child_info.@"struct".is_protocol) { + return self.toLLVMType(opt.child); + } + // ?T → { T, i1 } + var field_types: [2]c.LLVMTypeRef = .{ + self.toLLVMType(opt.child), + self.e.cached_i1, + }; + return c.LLVMStructTypeInContext(self.e.context, &field_types, 2, 0); + }, + .array => |arr| { + const elem = self.toLLVMType(arr.element); + return c.LLVMArrayType2(elem, arr.length); + }, + .vector => |vec| { + const elem = self.toLLVMType(vec.element); + return c.LLVMVectorType(elem, vec.length); + }, + .any => self.e.getAnyStructType(), + .noreturn => self.e.cached_void, + .@"struct" => |s| { + // Build LLVM struct type from fields + const n: c_uint = @intCast(s.fields.len); + const field_llvm_types = self.e.alloc.alloc(c.LLVMTypeRef, s.fields.len) catch unreachable; + defer self.e.alloc.free(field_llvm_types); + for (s.fields, 0..) |field, j| { + field_llvm_types[j] = self.toLLVMType(field.ty); + } + return c.LLVMStructTypeInContext(self.e.context, field_llvm_types.ptr, n, 0); + }, + .@"enum" => |e| { + // Use backing type if declared (e.g. enum u32 → i32), else i64 + if (e.backing_type) |bt| return self.toLLVMType(bt); + return self.e.cached_i64; + }, + .@"union" => |u| { + // Untagged union — just [N x i8] + var max_size: usize = 0; + for (u.fields) |field| { + const sz = self.e.ir_mod.types.typeSizeBytes(field.ty); + if (sz > max_size) max_size = sz; + } + if (max_size == 0) max_size = 8; + return c.LLVMArrayType2(self.e.cached_i8, @intCast(max_size)); + }, + .tagged_union => |u| { + // Tagged union — { header, [N x i8] } + var max_size: usize = 0; + for (u.fields) |field| { + const sz = self.e.ir_mod.types.typeSizeBytes(field.ty); + if (sz > max_size) max_size = sz; + } + if (max_size == 0) max_size = 8; + + var header_size: usize = self.e.ir_mod.types.typeSizeBytes(u.tag_type); + if (u.backing_type) |bt| { + const bi = self.e.ir_mod.types.get(bt); + if (bi == .@"struct" and bi.@"struct".fields.len > 1) { + header_size = 0; + const fields = bi.@"struct".fields; + for (fields[0 .. fields.len - 1]) |f| { + header_size += self.e.ir_mod.types.typeSizeBytes(f.ty); + } + const backing_payload = self.e.ir_mod.types.typeSizeBytes(fields[fields.len - 1].ty); + if (backing_payload > max_size) max_size = backing_payload; + } + } + + const header_llvm = c.LLVMIntTypeInContext(self.e.context, @intCast(header_size * 8)); + var field_types: [2]c.LLVMTypeRef = .{ + header_llvm, + c.LLVMArrayType2(self.e.cached_i8, @intCast(max_size)), + }; + return c.LLVMStructTypeInContext(self.e.context, &field_types, 2, 0); + }, + .tuple => |t| { + const n: c_uint = @intCast(t.fields.len); + const field_llvm_types = self.e.alloc.alloc(c.LLVMTypeRef, t.fields.len) catch unreachable; + defer self.e.alloc.free(field_llvm_types); + for (t.fields, 0..) |f, j| { + field_llvm_types[j] = self.toLLVMType(f); + } + return c.LLVMStructTypeInContext(self.e.context, field_llvm_types.ptr, n, 0); + }, + .protocol => { + // Protocol values: { ctx: *void, vtable_or_fn_ptrs... } + // For now, use opaque ptr + return self.e.cached_ptr; + }, + .usize, .isize => if (self.e.target_config.isWasm32()) self.e.cached_i32 else self.e.cached_i64, + // Comptime-only: a pack is expanded to flat positional args before + // codegen, so it must never reach LLVM type emission. + .pack => @panic("pack type has no LLVM representation (comptime-only)"), + // Tripwire: a failed type resolution must have been diagnosed and + // aborted long before LLVM emission. + .unresolved => @panic("unresolved type reached LLVM emission — a type resolution failure was not diagnosed/aborted"), + }; + } +}; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index a0c2deb..9f60884 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -10,6 +10,8 @@ const TypeInfo = ir_types.TypeInfo; const TypeTable = ir_types.TypeTable; const StringId = ir_types.StringId; const errors = @import("../errors.zig"); +const llvm_types = @import("../backend/llvm/types.zig"); +const llvm_abi = @import("../backend/llvm/abi.zig"); const ir_inst = @import("inst.zig"); const Ref = ir_inst.Ref; const Span = ir_inst.Span; @@ -4551,276 +4553,44 @@ pub const LLVMEmitter = struct { // ── Type conversion ───────────────────────────────────────────── - pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { - return switch (ty) { - .void => self.cached_void, - .bool => self.cached_i1, - .s8 => self.cached_i8, - .s16 => self.cached_i16, - .s32 => self.cached_i32, - .s64 => self.cached_i64, - .u8 => self.cached_i8, - .u16 => self.cached_i16, - .u32 => self.cached_i32, - .u64 => self.cached_i64, - .f32 => self.cached_f32, - .f64 => self.cached_f64, - .string => self.getStringStructType(), - .any => self.getAnyStructType(), - .noreturn => self.cached_void, - .isize, .usize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64, - else => self.toLLVMTypeInfo(ty), - }; + fn typeLowering(self: *LLVMEmitter) llvm_types.TypeLowering { + return .{ .e = self }; } - fn toLLVMTypeInfo(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { - const info = self.ir_mod.types.get(ty); - return switch (info) { - .signed => |w| switch (w) { - 1 => self.cached_i1, - 8 => self.cached_i8, - 16 => self.cached_i16, - 32 => self.cached_i32, - 64 => self.cached_i64, - else => c.LLVMIntTypeInContext(self.context, w), - }, - .unsigned => |w| switch (w) { - 1 => self.cached_i1, - 8 => self.cached_i8, - 16 => self.cached_i16, - 32 => self.cached_i32, - 64 => self.cached_i64, - else => c.LLVMIntTypeInContext(self.context, w), - }, - .f32 => self.cached_f32, - .f64 => self.cached_f64, - .void => self.cached_void, - .bool => self.cached_i1, - .error_set => self.cached_i32, // u32 tag id on the error channel - .string => self.getStringStructType(), - .pointer, .many_pointer, .function => self.cached_ptr, - .closure => self.getClosureStructType(), - .slice => self.getStringStructType(), // same {ptr, i64} layout - .optional => |opt| { - // ?*T / ?fn → bare pointer (null = none) - const child_info = self.ir_mod.types.get(opt.child); - if (child_info == .pointer or child_info == .many_pointer or child_info == .function) { - return self.cached_ptr; - } - if (child_info == .closure) { - return self.getClosureStructType(); - } - // ?Protocol → protocol struct (ctx ptr = field 0 is null when none). - if (child_info == .@"struct" and child_info.@"struct".is_protocol) { - return self.toLLVMType(opt.child); - } - // ?T → { T, i1 } - var field_types: [2]c.LLVMTypeRef = .{ - self.toLLVMType(opt.child), - self.cached_i1, - }; - return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); - }, - .array => |arr| { - const elem = self.toLLVMType(arr.element); - return c.LLVMArrayType2(elem, arr.length); - }, - .vector => |vec| { - const elem = self.toLLVMType(vec.element); - return c.LLVMVectorType(elem, vec.length); - }, - .any => self.getAnyStructType(), - .noreturn => self.cached_void, - .@"struct" => |s| { - // Build LLVM struct type from fields - const n: c_uint = @intCast(s.fields.len); - const field_llvm_types = self.alloc.alloc(c.LLVMTypeRef, s.fields.len) catch unreachable; - defer self.alloc.free(field_llvm_types); - for (s.fields, 0..) |field, j| { - field_llvm_types[j] = self.toLLVMType(field.ty); - } - return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0); - }, - .@"enum" => |e| { - // Use backing type if declared (e.g. enum u32 → i32), else i64 - if (e.backing_type) |bt| return self.toLLVMType(bt); - return self.cached_i64; - }, - .@"union" => |u| { - // Untagged union — just [N x i8] - var max_size: usize = 0; - for (u.fields) |field| { - const sz = self.ir_mod.types.typeSizeBytes(field.ty); - if (sz > max_size) max_size = sz; - } - if (max_size == 0) max_size = 8; - return c.LLVMArrayType2(self.cached_i8, @intCast(max_size)); - }, - .tagged_union => |u| { - // Tagged union — { header, [N x i8] } - var max_size: usize = 0; - for (u.fields) |field| { - const sz = self.ir_mod.types.typeSizeBytes(field.ty); - if (sz > max_size) max_size = sz; - } - if (max_size == 0) max_size = 8; + fn abiLowering(self: *LLVMEmitter) llvm_abi.AbiLowering { + return .{ .e = self }; + } - var header_size: usize = self.ir_mod.types.typeSizeBytes(u.tag_type); - if (u.backing_type) |bt| { - const bi = self.ir_mod.types.get(bt); - if (bi == .@"struct" and bi.@"struct".fields.len > 1) { - header_size = 0; - const fields = bi.@"struct".fields; - for (fields[0 .. fields.len - 1]) |f| { - header_size += self.ir_mod.types.typeSizeBytes(f.ty); - } - const backing_payload = self.ir_mod.types.typeSizeBytes(fields[fields.len - 1].ty); - if (backing_payload > max_size) max_size = backing_payload; - } - } - - const header_llvm = c.LLVMIntTypeInContext(self.context, @intCast(header_size * 8)); - var field_types: [2]c.LLVMTypeRef = .{ - header_llvm, - c.LLVMArrayType2(self.cached_i8, @intCast(max_size)), - }; - return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); - }, - .tuple => |t| { - const n: c_uint = @intCast(t.fields.len); - const field_llvm_types = self.alloc.alloc(c.LLVMTypeRef, t.fields.len) catch unreachable; - defer self.alloc.free(field_llvm_types); - for (t.fields, 0..) |f, j| { - field_llvm_types[j] = self.toLLVMType(f); - } - return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0); - }, - .protocol => { - // Protocol values: { ctx: *void, vtable_or_fn_ptrs... } - // For now, use opaque ptr - return self.cached_ptr; - }, - .usize, .isize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64, - // Comptime-only: a pack is expanded to flat positional args before - // codegen, so it must never reach LLVM type emission. - .pack => @panic("pack type has no LLVM representation (comptime-only)"), - // Tripwire: a failed type resolution must have been diagnosed and - // aborted long before LLVM emission. - .unresolved => @panic("unresolved type reached LLVM emission — a type resolution failure was not diagnosed/aborted"), - }; + /// IR-type → LLVM-type lowering lives in `backend/llvm/types.zig` + /// (`TypeLowering`). This stays the facade entry point (~97 callers). + pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { + return self.typeLowering().toLLVMType(ty); } // ── C ABI coercion for foreign functions ────────────────────────── - // - // On ARM64 (and x86_64), the C calling convention coerces small struct - // arguments to integers for register passing: - // - String/slice {ptr, i64} → ptr (extract raw pointer) - // - Small integer struct (≤ 8 bytes, non-HFA) → i64 - // - HFA (homogeneous float aggregate) → leave as-is (LLVM handles it) + // The coercion logic lives in `backend/llvm/abi.zig` (`AbiLowering`); + // these stay the facade entry points (callers in signature/call emission + + // the block-trampoline path use abiCoerceParamTypeEx directly). pub fn abiCoerceParamType(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef { - return self.abiCoerceParamTypeEx(ir_ty, llvm_ty, true); + return self.abiLowering().abiCoerceParamType(ir_ty, llvm_ty); } - /// Same as `abiCoerceParamType` but with an explicit - /// `is_foreign_c_api` knob. When true, sx `string` / `[]T` slices - /// collapse to `ptr` — the libc convention where the user writes - /// `string` to mean `char *` and the length is dropped. When - /// false (sx-internal `callconv(.c)` like block trampolines), the - /// full slice shape is preserved and goes through the general - /// struct-coerce path (16-byte slice → `[2 x i64]`, lands in two - /// registers on AArch64 — the true C ABI for a 16-byte - /// aggregate). Without the split, sx-to-sx calls through a - /// `(*Block, string) -> void callconv(.c)` fn-pointer mismatched - /// the caller's `{ptr, i64}` value against the trampoline's - /// collapsed `ptr` param. fn abiCoerceParamTypeEx(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef, is_foreign_c_api: bool) c.LLVMTypeRef { - if (is_foreign_c_api) { - if (ir_ty == .string) return self.cached_ptr; - if (!ir_ty.isBuiltin()) { - const info = self.ir_mod.types.get(ir_ty); - if (info == .slice) return self.cached_ptr; - } - } - - // WASM32: usize/isize are pointer-sized (i32 on wasm32). - // Other integer types (s64, u64) keep their declared size — they represent - // genuinely 64-bit values (SDL_WindowFlags, timestamps, etc.). - if (self.target_config.isWasm32()) { - if (ir_ty == .usize or ir_ty == .isize) return self.cached_i32; - return llvm_ty; - } - - // Only coerce struct types - if (c.LLVMGetTypeKind(llvm_ty) != c.LLVMStructTypeKind) return llvm_ty; - - // Check if it's an HFA (all float or all double fields) — leave as-is - const n_fields = c.LLVMCountStructElementTypes(llvm_ty); - if (n_fields >= 1 and n_fields <= 4) { - var all_float = true; - var all_double = true; - var fi: c_uint = 0; - while (fi < n_fields) : (fi += 1) { - const ft = c.LLVMStructGetTypeAtIndex(llvm_ty, fi); - const fk = c.LLVMGetTypeKind(ft); - if (fk != c.LLVMFloatTypeKind) all_float = false; - if (fk != c.LLVMDoubleTypeKind) all_double = false; - } - if (all_float or all_double) return llvm_ty; - } - - // Small struct (≤ 8 bytes) → coerce to i64 - const size = c.LLVMABISizeOfType( - c.LLVMGetModuleDataLayout(self.llvm_module), - llvm_ty, - ); - if (size <= 8) return self.cached_i64; - - // Medium struct (9-16 bytes) → coerce to [2 x i64] - if (size <= 16) { - return c.LLVMArrayType2(self.cached_i64, 2); - } - - // Large composite (> 16 bytes) → pass by reference: ptr + byval() at - // the call/sig sites. LLVM's AArch64/x86_64 backend lowers byval to - // the right ABI sequence (caller copy + indirect arg). - return self.cached_ptr; + return self.abiLowering().abiCoerceParamTypeEx(ir_ty, llvm_ty, is_foreign_c_api); } pub fn needsByval(self: *LLVMEmitter, ir_ty: TypeId, raw_llvm_ty: c.LLVMTypeRef) bool { - if (self.target_config.isWasm32()) return false; - if (ir_ty == .string) return false; - if (!ir_ty.isBuiltin()) { - const info = self.ir_mod.types.get(ir_ty); - if (info == .slice) return false; - } - if (c.LLVMGetTypeKind(raw_llvm_ty) != c.LLVMStructTypeKind) return false; - const n = c.LLVMCountStructElementTypes(raw_llvm_ty); - if (n >= 1 and n <= 4) { - var all_f = true; - var all_d = true; - var i: c_uint = 0; - while (i < n) : (i += 1) { - const ft = c.LLVMStructGetTypeAtIndex(raw_llvm_ty, i); - const fk = c.LLVMGetTypeKind(ft); - if (fk != c.LLVMFloatTypeKind) all_f = false; - if (fk != c.LLVMDoubleTypeKind) all_d = false; - } - if (all_f or all_d) return false; - } - const size = c.LLVMABISizeOfType(c.LLVMGetModuleDataLayout(self.llvm_module), raw_llvm_ty); - return size > 16; + return self.abiLowering().needsByval(ir_ty, raw_llvm_ty); } fn materializeByvalArg(self: *LLVMEmitter, val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef) c.LLVMValueRef { - const tmp = c.LLVMBuildAlloca(self.builder, struct_ty, "byval.tmp"); - _ = c.LLVMBuildStore(self.builder, val, tmp); - return tmp; + return self.abiLowering().materializeByvalArg(val, struct_ty); } // ── Cached composite types ────────────────────────────────────── - fn getStringStructType(self: *LLVMEmitter) c.LLVMTypeRef { + pub fn getStringStructType(self: *LLVMEmitter) c.LLVMTypeRef { if (self.string_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ self.cached_ptr, // ptr @@ -4892,7 +4662,7 @@ pub const LLVMEmitter = struct { return c.LLVMConstPtrToInt(g, self.cached_i64); } - fn getAnyStructType(self: *LLVMEmitter) c.LLVMTypeRef { + pub fn getAnyStructType(self: *LLVMEmitter) c.LLVMTypeRef { if (self.any_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ self.cached_i64, // type tag @@ -4902,7 +4672,7 @@ pub const LLVMEmitter = struct { return self.any_struct_type.?; } - fn getClosureStructType(self: *LLVMEmitter) c.LLVMTypeRef { + pub fn getClosureStructType(self: *LLVMEmitter) c.LLVMTypeRef { if (self.closure_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ self.cached_ptr, // fn_ptr From f92a743c853832a6932c18c4d20ba8acb464b1b1 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 09:22:40 +0300 Subject: [PATCH 04/26] refactor(backend): extract DWARF debug info into src/backend/llvm/debug.zig (A7.2 debug) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the DWARF debug-info emission out of emit_llvm.zig into a DebugInfo backend *LLVMEmitter facade (field `e`). Behavior-preserving relocation — self.* -> self.e.* only. - src/backend/llvm/debug.zig (DebugInfo): debugEnabled + diFileFor (private) + initDebugInfo / beginFunctionDebug / endFunctionDebug / setInstDebugLocation / finalizeDebugInfo (pub). The mutable DI state (di_builder/di_cu/di_files/ di_scope/current_func_file) + the shared source map (import_sources/main_file) stay on LLVMEmitter; the facade reads/writes them via self.e.*. - Routed the 5 pass-order call sites in LLVMEmitter.emit (init/finalize/ begin/end/setInstDebugLocation) through a new debugInfo() accessor. - setDebugContext stays on LLVMEmitter (shared-state setter; callers in main.zig/ core.zig/test). sourceForFile stays on LLVMEmitter and is widened to pub — it is shared with reflection's trace-frame emission (emitTraceFrame), not debug-only. - No DI logic / module-flag / DWARF-version / scope-line change. Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (no churn). --- src/backend/llvm/debug.zig | 160 +++++++++++++++++++++++++++++++++++++ src/ir/emit_llvm.zig | 155 +++-------------------------------- 2 files changed, 171 insertions(+), 144 deletions(-) create mode 100644 src/backend/llvm/debug.zig diff --git a/src/backend/llvm/debug.zig b/src/backend/llvm/debug.zig new file mode 100644 index 0000000..637c008 --- /dev/null +++ b/src/backend/llvm/debug.zig @@ -0,0 +1,160 @@ +const std = @import("std"); +const llvm = @import("../../llvm_api.zig"); +const c = llvm.c; +const errors = @import("../../errors.zig"); +const emit = @import("../../ir/emit_llvm.zig"); +const ir_inst = @import("../../ir/inst.zig"); + +const LLVMEmitter = emit.LLVMEmitter; +const Function = ir_inst.Function; +const Span = ir_inst.Span; + +/// DWARF debug-info emission (architecture phase A7.2), extracted from +/// `LLVMEmitter`. A backend `*LLVMEmitter` facade (field `e`): it owns the +/// `DIBuilder` lifecycle, the compile unit, per-function `DISubprogram` scopes, +/// and per-instruction `DILocation`s. The mutable DI state (`di_builder`/ +/// `di_cu`/`di_files`/`di_scope`/`current_func_file`) + the shared source map +/// (`import_sources`/`main_file`, also read by `#caller_location`) stay on +/// `LLVMEmitter`; this reads/writes them via `self.e.*`. `LLVMEmitter.emit` +/// drives the pass order and calls in via `self.debugInfo()`. +pub const DebugInfo = struct { + e: *LLVMEmitter, + + /// Debug info is emitted only when error traces are kept (opt_level + /// none/less, matching `tracesEnabled` in lower.zig) and a source + /// map is available. Release builds (default/aggressive) skip it, so + /// the DWARF is strippable cost-free. + fn debugEnabled(self: DebugInfo) bool { + if (self.e.import_sources == null) return false; + return self.e.target_config.opt_level == .none or self.e.target_config.opt_level == .less; + } + + /// The `DIFile` for `path`, created once and cached. Splits the path + /// into basename + directory as DWARF expects. The directory MUST be + /// non-empty: an empty `DW_AT_comp_dir` makes Apple's `ld` silently drop + /// the whole object's debug map (no `N_OSO`), so a binary built from a + /// bare filename (e.g. `sx build main.sx`) becomes undebuggable. Fall back + /// to "." when the path has no directory component. + fn diFileFor(self: DebugInfo, path: []const u8) c.LLVMMetadataRef { + if (self.e.di_files.get(path)) |f| return f; + const slash = std.mem.lastIndexOfScalar(u8, path, '/'); + const dir = if (slash) |s| (if (s == 0) "/" else path[0..s]) else "."; + const base = if (slash) |s| path[s + 1 ..] else path; + const f = c.LLVMDIBuilderCreateFile(self.e.di_builder, base.ptr, base.len, dir.ptr, dir.len); + self.e.di_files.put(path, f) catch {}; + return f; + } + + /// Create the DIBuilder, the module flags ("Debug Info Version" / + /// "Dwarf Version"), and the single compile unit on the main file. + pub fn initDebugInfo(self: DebugInfo) void { + if (!self.debugEnabled()) return; + self.e.di_builder = c.LLVMCreateDIBuilder(self.e.llvm_module); + + c.LLVMAddModuleFlag( + self.e.llvm_module, + c.LLVMModuleFlagBehaviorWarning, + "Debug Info Version", + "Debug Info Version".len, + c.LLVMValueAsMetadata(c.LLVMConstInt(self.e.cached_i32, c.LLVMDebugMetadataVersion(), 0)), + ); + c.LLVMAddModuleFlag( + self.e.llvm_module, + c.LLVMModuleFlagBehaviorWarning, + "Dwarf Version", + "Dwarf Version".len, + c.LLVMValueAsMetadata(c.LLVMConstInt(self.e.cached_i32, 4, 0)), + ); + + const cu_file = self.diFileFor(if (self.e.main_file.len > 0) self.e.main_file else "sx"); + self.e.di_cu = c.LLVMDIBuilderCreateCompileUnit( + self.e.di_builder, + c.LLVMDWARFSourceLanguageC, + cu_file, + "sx", + "sx".len, + 0, // isOptimized + "", + 0, // flags + 0, // runtime version + "", + 0, // split name + c.LLVMDWARFEmissionFull, + 0, // DWOId + 0, // split debug inlining + 0, // debug info for profiling + "", + 0, // sysroot + "", + 0, // sdk + ); + } + + /// Create a `DISubprogram` for `func` and attach it to `llvm_func`, + /// making it the scope (`di_scope`) for the function's instruction + /// locations. Clears any stale builder location first so synthetic + /// functions emitted between sx functions carry none. + pub fn beginFunctionDebug(self: DebugInfo, func: *const Function, llvm_func: c.LLVMValueRef, name: []const u8) void { + self.e.di_scope = null; + c.LLVMSetCurrentDebugLocation2(self.e.builder, null); + if (self.e.di_builder == null) return; + + const file = func.source_file orelse self.e.main_file; + self.e.current_func_file = file; + const di_file = self.diFileFor(file); + const subroutine_ty = c.LLVMDIBuilderCreateSubroutineType(self.e.di_builder, di_file, null, 0, c.LLVMDIFlagZero); + + // Line = the first instruction's line (the function body's start), + // else 1 when the body is empty / span-less. + var line: c_uint = 1; + if (func.blocks.items.len > 0 and func.blocks.items[0].insts.items.len > 0) { + const sp = func.blocks.items[0].insts.items[0].span; + const src = self.e.sourceForFile(file); + line = errors.SourceLoc.compute(src, sp.start).line; + } + + const is_local: c.LLVMBool = if (func.linkage == .external) 0 else 1; + const subprogram = c.LLVMDIBuilderCreateFunction( + self.e.di_builder, + di_file, // scope + name.ptr, + name.len, + name.ptr, + name.len, // linkage name + di_file, + line, + subroutine_ty, + is_local, + 1, // is definition + line, // scope line + c.LLVMDIFlagZero, + 0, // isOptimized + ); + c.LLVMSetSubprogram(llvm_func, subprogram); + self.e.di_scope = subprogram; + } + + /// End the current function's debug scope and clear the builder's + /// location, so the next (possibly synthetic) function doesn't + /// inherit a DILocation pointing into this function's subprogram. + pub fn endFunctionDebug(self: DebugInfo) void { + self.e.di_scope = null; + c.LLVMSetCurrentDebugLocation2(self.e.builder, null); + } + + /// Set the builder's current debug location from an instruction span, + /// scoped to the current function's subprogram. No-op when debug info + /// is off (`di_scope == null`). + pub fn setInstDebugLocation(self: DebugInfo, span: Span) void { + const scope = self.e.di_scope orelse return; + const src = self.e.sourceForFile(self.e.current_func_file); + const loc = errors.SourceLoc.compute(src, span.start); + const di_loc = c.LLVMDIBuilderCreateDebugLocation(self.e.context, loc.line, loc.col, scope, null); + c.LLVMSetCurrentDebugLocation2(self.e.builder, di_loc); + } + + pub fn finalizeDebugInfo(self: DebugInfo) void { + if (self.e.di_builder == null) return; + c.LLVMDIBuilderFinalize(self.e.di_builder); + } +}; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 9f60884..5593048 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -12,6 +12,7 @@ const StringId = ir_types.StringId; const errors = @import("../errors.zig"); const llvm_types = @import("../backend/llvm/types.zig"); const llvm_abi = @import("../backend/llvm/abi.zig"); +const llvm_debug = @import("../backend/llvm/debug.zig"); const ir_inst = @import("inst.zig"); const Ref = ir_inst.Ref; const Span = ir_inst.Span; @@ -323,7 +324,7 @@ pub const LLVMEmitter = struct { pub fn emit(self: *LLVMEmitter) void { // Pass -1: Set up DWARF debug info (compile unit + module flags). // Must precede any DISubprogram (created per function below). - self.initDebugInfo(); + self.debugInfo().initDebugInfo(); // Pass 0: Declare and initialize globals self.emitGlobals(); @@ -373,7 +374,7 @@ pub const LLVMEmitter = struct { // Pass 4: Resolve DWARF temporary metadata. Must come after all // DISubprograms / DILocations are created and before the module // is verified or emitted. - self.finalizeDebugInfo(); + self.debugInfo().finalizeDebugInfo(); } // ── DWARF debug info (ERR E3.0) ────────────────────────────────── @@ -386,19 +387,10 @@ pub const LLVMEmitter = struct { self.main_file = main_file; } - /// Debug info is emitted only when error traces are kept (opt_level - /// none/less, matching `tracesEnabled` in lower.zig) and a source - /// map is available. Release builds (default/aggressive) skip it, so - /// the DWARF is strippable cost-free. - fn debugEnabled(self: *const LLVMEmitter) bool { - if (self.import_sources == null) return false; - return self.target_config.opt_level == .none or self.target_config.opt_level == .less; - } - /// Source text for `file` via the diagnostics' file→source map (the /// same map `#caller_location` uses). Empty when unavailable — /// line:col then degrades to 1:1 rather than crash. - fn sourceForFile(self: *LLVMEmitter, file: []const u8) []const u8 { + pub fn sourceForFile(self: *LLVMEmitter, file: []const u8) []const u8 { const is = self.import_sources orelse return ""; if (is.get(file)) |s| return s; if (self.main_file.len > 0) { @@ -407,135 +399,6 @@ pub const LLVMEmitter = struct { return ""; } - /// The `DIFile` for `path`, created once and cached. Splits the path - /// into basename + directory as DWARF expects. The directory MUST be - /// non-empty: an empty `DW_AT_comp_dir` makes Apple's `ld` silently drop - /// the whole object's debug map (no `N_OSO`), so a binary built from a - /// bare filename (e.g. `sx build main.sx`) becomes undebuggable. Fall back - /// to "." when the path has no directory component. - fn diFileFor(self: *LLVMEmitter, path: []const u8) c.LLVMMetadataRef { - if (self.di_files.get(path)) |f| return f; - const slash = std.mem.lastIndexOfScalar(u8, path, '/'); - const dir = if (slash) |s| (if (s == 0) "/" else path[0..s]) else "."; - const base = if (slash) |s| path[s + 1 ..] else path; - const f = c.LLVMDIBuilderCreateFile(self.di_builder, base.ptr, base.len, dir.ptr, dir.len); - self.di_files.put(path, f) catch {}; - return f; - } - - /// Create the DIBuilder, the module flags ("Debug Info Version" / - /// "Dwarf Version"), and the single compile unit on the main file. - fn initDebugInfo(self: *LLVMEmitter) void { - if (!self.debugEnabled()) return; - self.di_builder = c.LLVMCreateDIBuilder(self.llvm_module); - - c.LLVMAddModuleFlag( - self.llvm_module, - c.LLVMModuleFlagBehaviorWarning, - "Debug Info Version", - "Debug Info Version".len, - c.LLVMValueAsMetadata(c.LLVMConstInt(self.cached_i32, c.LLVMDebugMetadataVersion(), 0)), - ); - c.LLVMAddModuleFlag( - self.llvm_module, - c.LLVMModuleFlagBehaviorWarning, - "Dwarf Version", - "Dwarf Version".len, - c.LLVMValueAsMetadata(c.LLVMConstInt(self.cached_i32, 4, 0)), - ); - - const cu_file = self.diFileFor(if (self.main_file.len > 0) self.main_file else "sx"); - self.di_cu = c.LLVMDIBuilderCreateCompileUnit( - self.di_builder, - c.LLVMDWARFSourceLanguageC, - cu_file, - "sx", - "sx".len, - 0, // isOptimized - "", - 0, // flags - 0, // runtime version - "", - 0, // split name - c.LLVMDWARFEmissionFull, - 0, // DWOId - 0, // split debug inlining - 0, // debug info for profiling - "", - 0, // sysroot - "", - 0, // sdk - ); - } - - /// Create a `DISubprogram` for `func` and attach it to `llvm_func`, - /// making it the scope (`di_scope`) for the function's instruction - /// locations. Clears any stale builder location first so synthetic - /// functions emitted between sx functions carry none. - fn beginFunctionDebug(self: *LLVMEmitter, func: *const Function, llvm_func: c.LLVMValueRef, name: []const u8) void { - self.di_scope = null; - c.LLVMSetCurrentDebugLocation2(self.builder, null); - if (self.di_builder == null) return; - - const file = func.source_file orelse self.main_file; - self.current_func_file = file; - const di_file = self.diFileFor(file); - const subroutine_ty = c.LLVMDIBuilderCreateSubroutineType(self.di_builder, di_file, null, 0, c.LLVMDIFlagZero); - - // Line = the first instruction's line (the function body's start), - // else 1 when the body is empty / span-less. - var line: c_uint = 1; - if (func.blocks.items.len > 0 and func.blocks.items[0].insts.items.len > 0) { - const sp = func.blocks.items[0].insts.items[0].span; - const src = self.sourceForFile(file); - line = errors.SourceLoc.compute(src, sp.start).line; - } - - const is_local: c.LLVMBool = if (func.linkage == .external) 0 else 1; - const subprogram = c.LLVMDIBuilderCreateFunction( - self.di_builder, - di_file, // scope - name.ptr, - name.len, - name.ptr, - name.len, // linkage name - di_file, - line, - subroutine_ty, - is_local, - 1, // is definition - line, // scope line - c.LLVMDIFlagZero, - 0, // isOptimized - ); - c.LLVMSetSubprogram(llvm_func, subprogram); - self.di_scope = subprogram; - } - - /// End the current function's debug scope and clear the builder's - /// location, so the next (possibly synthetic) function doesn't - /// inherit a DILocation pointing into this function's subprogram. - fn endFunctionDebug(self: *LLVMEmitter) void { - self.di_scope = null; - c.LLVMSetCurrentDebugLocation2(self.builder, null); - } - - /// Set the builder's current debug location from an instruction span, - /// scoped to the current function's subprogram. No-op when debug info - /// is off (`di_scope == null`). - fn setInstDebugLocation(self: *LLVMEmitter, span: Span) void { - const scope = self.di_scope orelse return; - const src = self.sourceForFile(self.current_func_file); - const loc = errors.SourceLoc.compute(src, span.start); - const di_loc = c.LLVMDIBuilderCreateDebugLocation(self.context, loc.line, loc.col, scope, null); - c.LLVMSetCurrentDebugLocation2(self.builder, di_loc); - } - - fn finalizeDebugInfo(self: *LLVMEmitter) void { - if (self.di_builder == null) return; - c.LLVMDIBuilderFinalize(self.di_builder); - } - /// Synthesize a module constructor that populates each interned /// Obj-C selector slot via `sel_registerName`, once at module /// load. Registered in `@llvm.global_ctors` so dyld / ld.so / the @@ -1868,7 +1731,7 @@ pub const LLVMEmitter = struct { // DWARF: describe this function and make it the scope for the // per-instruction locations set in emitInst (no-op if off). - self.beginFunctionDebug(func, llvm_func, name); + self.debugInfo().beginFunctionDebug(func, llvm_func, name); // Clear ref_map and pre-map parameter refs self.ref_map.clearRetainingCapacity(); @@ -1930,7 +1793,7 @@ pub const LLVMEmitter = struct { self.fixupPhiNodes(func, func_idx); // DWARF: leave no stale location for the next function. - self.endFunctionDebug(); + self.debugInfo().endFunctionDebug(); } /// After emitting all blocks, fill in PHI incoming values from branch args. @@ -1982,7 +1845,7 @@ pub const LLVMEmitter = struct { fn emitInst(self: *LLVMEmitter, instruction: *const Inst, func_idx: u32) void { // DWARF: stamp every LLVM instruction this op emits with the sx // source location (no-op when debug info is off). - self.setInstDebugLocation(instruction.span); + self.debugInfo().setInstDebugLocation(instruction.span); switch (instruction.op) { // ── Constants ─────────────────────────────────────────── .const_int => |val| { @@ -4561,6 +4424,10 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } + fn debugInfo(self: *LLVMEmitter) llvm_debug.DebugInfo { + return .{ .e = self }; + } + /// IR-type → LLVM-type lowering lives in `backend/llvm/types.zig` /// (`TypeLowering`). This stays the facade entry point (~97 callers). pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { From 46b874074b5c198a1202a137532bd096d8e2f4fb Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 09:29:27 +0300 Subject: [PATCH 05/26] refactor(backend): extract reflection metadata + trace frames into src/backend/llvm/reflection.zig (A7.2 reflection) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the type/field/tag reflection name-array builders and the error-trace Frame builders out of emit_llvm.zig into a Reflection backend *LLVMEmitter facade (field `e`). Behavior-preserving relocation — self.* -> self.e.* only. - src/backend/llvm/reflection.zig (Reflection): getOrBuildTypeNameArray / getOrBuildFieldNameArray / getOrBuildTagNameArray (pub) + emitTraceFrame (pub) + buildStringConst (private trace helper). The memoized state (type_name_array(_len) / field_name_arrays / tag_name_array / frame_str_cache) stays on LLVMEmitter; the facade reads/writes via self.e.*. - Routed the 5 call sites through a new reflection() accessor (type_name / field_name / error_tag_name builtins, emitFailableMainRet's tag-name lookup, and the .trace_frame push). - Kept in emit_llvm.zig per the A6.1 "emission-heavy stays" precedent: getFrameStructType (composite-type getter, widened to pub — emitTraceFrame calls it back), emitFieldValueGet (field-value reflection EMISSION, not an array builder), emitFailableMainRet. getStringStructType/getAnyStructType already pub. - No reflection-array layout, trace-Frame field order, or linkage change. Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (reflection anchors 0030/0118/0517/0520 + trace anchors 1024/1025/1026 all ok, no churn). --- src/backend/llvm/reflection.zig | 205 ++++++++++++++++++++++++++++++++ src/ir/emit_llvm.zig | 205 ++------------------------------ 2 files changed, 216 insertions(+), 194 deletions(-) create mode 100644 src/backend/llvm/reflection.zig diff --git a/src/backend/llvm/reflection.zig b/src/backend/llvm/reflection.zig new file mode 100644 index 0000000..e85f2ac --- /dev/null +++ b/src/backend/llvm/reflection.zig @@ -0,0 +1,205 @@ +const std = @import("std"); +const llvm = @import("../../llvm_api.zig"); +const c = llvm.c; +const errors = @import("../../errors.zig"); +const emit = @import("../../ir/emit_llvm.zig"); +const ir_inst = @import("../../ir/inst.zig"); +const ir_types = @import("../../ir/types.zig"); + +const LLVMEmitter = emit.LLVMEmitter; +const Inst = ir_inst.Inst; +const TypeId = ir_types.TypeId; +const StringId = ir_types.StringId; + +/// Reflection metadata + trace-frame emission (architecture phase A7.2), +/// extracted from `LLVMEmitter`. A backend `*LLVMEmitter` facade (field `e`): +/// the type/field/tag reflection NAME-ARRAY builders (memoized into +/// `type_name_array`/`field_name_arrays`/`tag_name_array` on `LLVMEmitter`) and +/// the error-trace `Frame` builders. Reads cached LLVM handles / the IR type +/// table / the module via `self.e.*`; the memoizing composite getters +/// (`getStringStructType`/`getFrameStructType`) + `emitFieldValueGet` stay on +/// `LLVMEmitter`. Entry points are reached via `self.reflection()`. +pub const Reflection = struct { + e: *LLVMEmitter, + + /// Lazy global `[N x string]` indexed by `TypeId.index()`, holding each + /// type's display name. Built on the first dynamic `type_name(t)` call site. + pub fn getOrBuildTypeNameArray(self: Reflection) c.LLVMValueRef { + if (self.e.type_name_array) |g| return g; + + const n: u32 = @intCast(self.e.ir_mod.types.infos.items.len); + const string_ty = self.e.getStringStructType(); + + var field_vals = std.ArrayList(c.LLVMValueRef).empty; + defer field_vals.deinit(self.e.alloc); + var i: u32 = 0; + while (i < n) : (i += 1) { + const tid = TypeId.fromIndex(i); + const name_str = self.e.ir_mod.types.formatTypeName(self.e.alloc, tid); + const str_z = self.e.alloc.dupeZ(u8, name_str) catch unreachable; + defer self.e.alloc.free(str_z); + const global_str = c.LLVMAddGlobal(self.e.llvm_module, c.LLVMArrayType(self.e.cached_i8, @intCast(name_str.len + 1)), "tn.str"); + c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.e.context, str_z.ptr, @intCast(name_str.len + 1), 1)); + c.LLVMSetGlobalConstant(global_str, 1); + c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage); + const len_val = c.LLVMConstInt(self.e.cached_i64, name_str.len, 0); + var struct_fields = [2]c.LLVMValueRef{ global_str, len_val }; + const const_struct = c.LLVMConstStructInContext(self.e.context, &struct_fields, 2, 0); + field_vals.append(self.e.alloc, const_struct) catch unreachable; + } + + const arr_ty = c.LLVMArrayType(string_ty, n); + const arr_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n); + const global = c.LLVMAddGlobal(self.e.llvm_module, arr_ty, "__sx_type_names"); + c.LLVMSetInitializer(global, arr_init); + c.LLVMSetGlobalConstant(global, 1); + c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); + + self.e.type_name_array = global; + self.e.type_name_array_len = n; + return global; + } + + /// Build (or return cached) a global constant array of {ptr, i64} string values + /// for the field names of a struct type. + pub fn getOrBuildFieldNameArray(self: Reflection, struct_type: TypeId) c.LLVMValueRef { + if (self.e.field_name_arrays.get(struct_type.index())) |g| return g; + + const info = self.e.ir_mod.types.get(struct_type); + + // Collect name StringIds from struct fields, union fields, or enum variants + var name_ids = std.ArrayList(StringId).empty; + defer name_ids.deinit(self.e.alloc); + switch (info) { + .@"struct" => |s| { + for (s.fields) |f| name_ids.append(self.e.alloc, f.name) catch unreachable; + }, + .@"union" => |u| { + for (u.fields) |f| name_ids.append(self.e.alloc, f.name) catch unreachable; + }, + .tagged_union => |u| { + for (u.fields) |f| name_ids.append(self.e.alloc, f.name) catch unreachable; + }, + .@"enum" => |e| { + for (e.variants) |v| name_ids.append(self.e.alloc, v) catch unreachable; + }, + else => {}, + } + + const string_ty = self.e.getStringStructType(); + const n: u32 = @intCast(name_ids.items.len); + + // Build constant initializer: [N x {ptr, i64}] + var field_vals = std.ArrayList(c.LLVMValueRef).empty; + defer field_vals.deinit(self.e.alloc); + for (name_ids.items) |name_id| { + const name_str = self.e.ir_mod.types.getString(name_id); + const str_z = self.e.alloc.dupeZ(u8, name_str) catch unreachable; + defer self.e.alloc.free(str_z); + const global_str = c.LLVMAddGlobal(self.e.llvm_module, c.LLVMArrayType(self.e.cached_i8, @intCast(name_str.len + 1)), "fld.str"); + c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.e.context, str_z.ptr, @intCast(name_str.len + 1), 1)); + c.LLVMSetGlobalConstant(global_str, 1); + c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage); + // Build fat pointer {ptr, len} as constant struct + const len_val = c.LLVMConstInt(self.e.cached_i64, name_str.len, 0); + var struct_fields = [2]c.LLVMValueRef{ global_str, len_val }; + const const_struct = c.LLVMConstStructInContext(self.e.context, &struct_fields, 2, 0); + field_vals.append(self.e.alloc, const_struct) catch unreachable; + } + + // Create global array [N x {ptr, i64}] + const array_ty = c.LLVMArrayType(string_ty, n); + const array_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n); + const global = c.LLVMAddGlobal(self.e.llvm_module, array_ty, "field_names"); + c.LLVMSetInitializer(global, array_init); + c.LLVMSetGlobalConstant(global, 1); + c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); + + self.e.field_name_arrays.put(struct_type.index(), global) catch unreachable; + return global; + } + + /// The always-linked tag-name table: a `[N x {ptr, i64}]` global of tag + /// names indexed by global tag id (the `TagRegistry` namespace; slot 0 is + /// the reserved "" no-error name). `error_tag_name_get` GEPs into it at the + /// runtime tag id. Built once per module. Always emitted (not trace-gated) + /// so `{}` interpolation of an error tag works even in release builds. + pub fn getOrBuildTagNameArray(self: Reflection) c.LLVMValueRef { + if (self.e.tag_name_array) |g| return g; + + const string_ty = self.e.getStringStructType(); + const names = self.e.ir_mod.types.tags.names.items; + + var field_vals = std.ArrayList(c.LLVMValueRef).empty; + defer field_vals.deinit(self.e.alloc); + for (names) |name_str| { + const str_z = self.e.alloc.dupeZ(u8, name_str) catch unreachable; + defer self.e.alloc.free(str_z); + const global_str = c.LLVMAddGlobal(self.e.llvm_module, c.LLVMArrayType(self.e.cached_i8, @intCast(name_str.len + 1)), "tag.str"); + c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.e.context, str_z.ptr, @intCast(name_str.len + 1), 1)); + c.LLVMSetGlobalConstant(global_str, 1); + c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage); + const len_val = c.LLVMConstInt(self.e.cached_i64, name_str.len, 0); + var struct_fields = [2]c.LLVMValueRef{ global_str, len_val }; + const const_struct = c.LLVMConstStructInContext(self.e.context, &struct_fields, 2, 0); + field_vals.append(self.e.alloc, const_struct) catch unreachable; + } + + const n: u32 = @intCast(names.len); + const array_ty = c.LLVMArrayType(string_ty, n); + const array_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n); + const global = c.LLVMAddGlobal(self.e.llvm_module, array_ty, "tag_names"); + c.LLVMSetInitializer(global, array_init); + c.LLVMSetGlobalConstant(global, 1); + c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); + + self.e.tag_name_array = global; + return global; + } + + /// An interned constant sx `string` (`{ ptr, i64 }`) of the cached string + /// struct type, backed by a private NUL-terminated data global. Cached by + /// content so a path/name shared by many push sites is emitted once. + fn buildStringConst(self: Reflection, s: []const u8) c.LLVMValueRef { + if (self.e.frame_str_cache.get(s)) |v| return v; + const str_z = self.e.alloc.dupeZ(u8, s) catch unreachable; + defer self.e.alloc.free(str_z); + const data = c.LLVMAddGlobal(self.e.llvm_module, c.LLVMArrayType(self.e.cached_i8, @intCast(s.len + 1)), "frame.str"); + c.LLVMSetInitializer(data, c.LLVMConstStringInContext(self.e.context, str_z.ptr, @intCast(s.len + 1), 1)); + c.LLVMSetGlobalConstant(data, 1); + c.LLVMSetLinkage(data, c.LLVMPrivateLinkage); + c.LLVMSetUnnamedAddress(data, c.LLVMGlobalUnnamedAddr); + var fields = [_]c.LLVMValueRef{ data, c.LLVMConstInt(self.e.cached_i64, s.len, 0) }; + const str_const = c.LLVMConstNamedStruct(self.e.getStringStructType(), &fields, 2); + const key = self.e.alloc.dupe(u8, s) catch return str_const; + self.e.frame_str_cache.put(key, str_const) catch self.e.alloc.free(key); + return str_const; + } + + /// Build the interned `Frame` global for a `.trace_frame` push site and + /// return its address as `i64` (the value `sx_trace_push` stores). Resolves + /// the instruction's span + current function to `{file,line,col,func}`. The + /// file is shown as its basename so trace output is machine-independent + /// (the harness passes absolute paths); full paths live in DWARF. + pub fn emitTraceFrame(self: Reflection, instruction: *const Inst) c.LLVMValueRef { + const file = std.fs.path.basename(self.e.current_func_file); + const src = self.e.sourceForFile(self.e.current_func_file); + const loc = errors.SourceLoc.compute(src, instruction.span.start); + const func_name = self.e.ir_mod.types.getString(self.e.ir_mod.functions.items[self.e.current_func_idx].name); + + var fields = [_]c.LLVMValueRef{ + self.buildStringConst(file), + c.LLVMConstInt(self.e.cached_i32, loc.line, 0), + c.LLVMConstInt(self.e.cached_i32, loc.col, 0), + self.buildStringConst(func_name), + self.buildStringConst(errors.lineAt(src, instruction.span.start)), + }; + const frame_ty = self.e.getFrameStructType(); + const frame_const = c.LLVMConstNamedStruct(frame_ty, &fields, 5); + const g = c.LLVMAddGlobal(self.e.llvm_module, frame_ty, "trace.frame"); + c.LLVMSetInitializer(g, frame_const); + c.LLVMSetGlobalConstant(g, 1); + c.LLVMSetLinkage(g, c.LLVMPrivateLinkage); + return c.LLVMConstPtrToInt(g, self.e.cached_i64); + } +}; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 5593048..2b604fb 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -13,6 +13,7 @@ const errors = @import("../errors.zig"); const llvm_types = @import("../backend/llvm/types.zig"); const llvm_abi = @import("../backend/llvm/abi.zig"); const llvm_debug = @import("../backend/llvm/debug.zig"); +const llvm_reflection = @import("../backend/llvm/reflection.zig"); const ir_inst = @import("inst.zig"); const Ref = ir_inst.Ref; const Span = ir_inst.Span; @@ -1880,7 +1881,7 @@ pub const LLVMEmitter = struct { self.advanceRefCounter(); }, .trace_frame => { - self.mapRef(self.emitTraceFrame(instruction)); + self.mapRef(self.reflection().emitTraceFrame(instruction)); }, .trace_resolve => |u| { // The operand is a `Frame*` stamped in by `.trace_frame` (as @@ -3264,7 +3265,7 @@ pub const LLVMEmitter = struct { // Bare i64 (TypeId index). break :blk arg_val; }; - const arr_global = self.getOrBuildTypeNameArray(); + const arr_global = self.reflection().getOrBuildTypeNameArray(); const arr_len = self.type_name_array_len; const string_ty = self.getStringStructType(); const arr_ty = c.LLVMArrayType(string_ty, arr_len); @@ -3543,7 +3544,7 @@ pub const LLVMEmitter = struct { // ── Reflection ops ────────────────────────────────────── .field_name_get => |fr| { // Build global string array for this struct's field names, then GEP at runtime index - const global = self.getOrBuildFieldNameArray(fr.struct_type); + const global = self.reflection().getOrBuildFieldNameArray(fr.struct_type); const idx = self.resolveRef(fr.index); const string_ty = self.getStringStructType(); // Get struct field count for array type @@ -3571,7 +3572,7 @@ pub const LLVMEmitter = struct { // the runtime tag id (the error-set value, a u32). Out-of-range // ids can't occur — ids come from the same registry the table // is built from — so no bounds branch is needed. - const global = self.getOrBuildTagNameArray(); + const global = self.reflection().getOrBuildTagNameArray(); const tag_raw = self.resolveRef(u.operand); const idx = c.LLVMBuildZExt(self.builder, tag_raw, self.cached_i64, "etn.idx"); const string_ty = self.getStringStructType(); @@ -4428,6 +4429,10 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } + fn reflection(self: *LLVMEmitter) llvm_reflection.Reflection { + return .{ .e = self }; + } + /// IR-type → LLVM-type lowering lives in `backend/llvm/types.zig` /// (`TypeLowering`). This stays the facade entry point (~97 callers). pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { @@ -4469,7 +4474,7 @@ pub const LLVMEmitter = struct { /// The compiled error-trace `Frame` type: `{ string, i32, i32, string }`. /// Layout must match `Frame` in `trace.sx` and `SxFrame` in `sx_trace.c`. - fn getFrameStructType(self: *LLVMEmitter) c.LLVMTypeRef { + pub fn getFrameStructType(self: *LLVMEmitter) c.LLVMTypeRef { if (self.frame_struct_type) |t| return t; const str_ty = self.getStringStructType(); var field_types = [_]c.LLVMTypeRef{ @@ -4483,52 +4488,6 @@ pub const LLVMEmitter = struct { return self.frame_struct_type.?; } - /// An interned constant sx `string` (`{ ptr, i64 }`) of the cached string - /// struct type, backed by a private NUL-terminated data global. Cached by - /// content so a path/name shared by many push sites is emitted once. - fn buildStringConst(self: *LLVMEmitter, s: []const u8) c.LLVMValueRef { - if (self.frame_str_cache.get(s)) |v| return v; - const str_z = self.alloc.dupeZ(u8, s) catch unreachable; - defer self.alloc.free(str_z); - const data = c.LLVMAddGlobal(self.llvm_module, c.LLVMArrayType(self.cached_i8, @intCast(s.len + 1)), "frame.str"); - c.LLVMSetInitializer(data, c.LLVMConstStringInContext(self.context, str_z.ptr, @intCast(s.len + 1), 1)); - c.LLVMSetGlobalConstant(data, 1); - c.LLVMSetLinkage(data, c.LLVMPrivateLinkage); - c.LLVMSetUnnamedAddress(data, c.LLVMGlobalUnnamedAddr); - var fields = [_]c.LLVMValueRef{ data, c.LLVMConstInt(self.cached_i64, s.len, 0) }; - const str_const = c.LLVMConstNamedStruct(self.getStringStructType(), &fields, 2); - const key = self.alloc.dupe(u8, s) catch return str_const; - self.frame_str_cache.put(key, str_const) catch self.alloc.free(key); - return str_const; - } - - /// Build the interned `Frame` global for a `.trace_frame` push site and - /// return its address as `i64` (the value `sx_trace_push` stores). Resolves - /// the instruction's span + current function to `{file,line,col,func}`. The - /// file is shown as its basename so trace output is machine-independent - /// (the harness passes absolute paths); full paths live in DWARF. - fn emitTraceFrame(self: *LLVMEmitter, instruction: *const Inst) c.LLVMValueRef { - const file = std.fs.path.basename(self.current_func_file); - const src = self.sourceForFile(self.current_func_file); - const loc = errors.SourceLoc.compute(src, instruction.span.start); - const func_name = self.ir_mod.types.getString(self.ir_mod.functions.items[self.current_func_idx].name); - - var fields = [_]c.LLVMValueRef{ - self.buildStringConst(file), - c.LLVMConstInt(self.cached_i32, loc.line, 0), - c.LLVMConstInt(self.cached_i32, loc.col, 0), - self.buildStringConst(func_name), - self.buildStringConst(errors.lineAt(src, instruction.span.start)), - }; - const frame_ty = self.getFrameStructType(); - const frame_const = c.LLVMConstNamedStruct(frame_ty, &fields, 5); - const g = c.LLVMAddGlobal(self.llvm_module, frame_ty, "trace.frame"); - c.LLVMSetInitializer(g, frame_const); - c.LLVMSetGlobalConstant(g, 1); - c.LLVMSetLinkage(g, c.LLVMPrivateLinkage); - return c.LLVMConstPtrToInt(g, self.cached_i64); - } - pub fn getAnyStructType(self: *LLVMEmitter) c.LLVMTypeRef { if (self.any_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ @@ -4671,110 +4630,6 @@ pub const LLVMEmitter = struct { self.mapRef(result); } - // ── Reflection emission helpers ──────────────────────────────── - - /// Build (or return cached) a global constant array of {ptr, i64} - /// string values indexed by `TypeId.index()`. Lets the dynamic - /// `type_name(t)` builtin look up the type's display name at - /// runtime — `gep arr[tid]; load string`. The array's length is - /// the current `infos.items.len`; new types interned after this - /// is built fall outside the array (the gep would OOB), so - /// callers must build LAZILY after all types are registered. - fn getOrBuildTypeNameArray(self: *LLVMEmitter) c.LLVMValueRef { - if (self.type_name_array) |g| return g; - - const n: u32 = @intCast(self.ir_mod.types.infos.items.len); - const string_ty = self.getStringStructType(); - - var field_vals = std.ArrayList(c.LLVMValueRef).empty; - defer field_vals.deinit(self.alloc); - var i: u32 = 0; - while (i < n) : (i += 1) { - const tid = @import("types.zig").TypeId.fromIndex(i); - const name_str = self.ir_mod.types.formatTypeName(self.alloc, tid); - const str_z = self.alloc.dupeZ(u8, name_str) catch unreachable; - defer self.alloc.free(str_z); - const global_str = c.LLVMAddGlobal(self.llvm_module, c.LLVMArrayType(self.cached_i8, @intCast(name_str.len + 1)), "tn.str"); - c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.context, str_z.ptr, @intCast(name_str.len + 1), 1)); - c.LLVMSetGlobalConstant(global_str, 1); - c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage); - const len_val = c.LLVMConstInt(self.cached_i64, name_str.len, 0); - var struct_fields = [2]c.LLVMValueRef{ global_str, len_val }; - const const_struct = c.LLVMConstStructInContext(self.context, &struct_fields, 2, 0); - field_vals.append(self.alloc, const_struct) catch unreachable; - } - - const arr_ty = c.LLVMArrayType(string_ty, n); - const arr_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n); - const global = c.LLVMAddGlobal(self.llvm_module, arr_ty, "__sx_type_names"); - c.LLVMSetInitializer(global, arr_init); - c.LLVMSetGlobalConstant(global, 1); - c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); - - self.type_name_array = global; - self.type_name_array_len = n; - return global; - } - - /// Build (or return cached) a global constant array of {ptr, i64} string values - /// for the field names of a struct type. - fn getOrBuildFieldNameArray(self: *LLVMEmitter, struct_type: TypeId) c.LLVMValueRef { - if (self.field_name_arrays.get(struct_type.index())) |g| return g; - - const info = self.ir_mod.types.get(struct_type); - - // Collect name StringIds from struct fields, union fields, or enum variants - var name_ids = std.ArrayList(StringId).empty; - defer name_ids.deinit(self.alloc); - switch (info) { - .@"struct" => |s| { - for (s.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable; - }, - .@"union" => |u| { - for (u.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable; - }, - .tagged_union => |u| { - for (u.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable; - }, - .@"enum" => |e| { - for (e.variants) |v| name_ids.append(self.alloc, v) catch unreachable; - }, - else => {}, - } - - const string_ty = self.getStringStructType(); - const n: u32 = @intCast(name_ids.items.len); - - // Build constant initializer: [N x {ptr, i64}] - var field_vals = std.ArrayList(c.LLVMValueRef).empty; - defer field_vals.deinit(self.alloc); - for (name_ids.items) |name_id| { - const name_str = self.ir_mod.types.getString(name_id); - const str_z = self.alloc.dupeZ(u8, name_str) catch unreachable; - defer self.alloc.free(str_z); - const global_str = c.LLVMAddGlobal(self.llvm_module, c.LLVMArrayType(self.cached_i8, @intCast(name_str.len + 1)), "fld.str"); - c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.context, str_z.ptr, @intCast(name_str.len + 1), 1)); - c.LLVMSetGlobalConstant(global_str, 1); - c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage); - // Build fat pointer {ptr, len} as constant struct - const len_val = c.LLVMConstInt(self.cached_i64, name_str.len, 0); - var struct_fields = [2]c.LLVMValueRef{ global_str, len_val }; - const const_struct = c.LLVMConstStructInContext(self.context, &struct_fields, 2, 0); - field_vals.append(self.alloc, const_struct) catch unreachable; - } - - // Create global array [N x {ptr, i64}] - const array_ty = c.LLVMArrayType(string_ty, n); - const array_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n); - const global = c.LLVMAddGlobal(self.llvm_module, array_ty, "field_names"); - c.LLVMSetInitializer(global, array_init); - c.LLVMSetGlobalConstant(global, 1); - c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); - - self.field_name_arrays.put(struct_type.index(), global) catch unreachable; - return global; - } - /// Failable main entry-point wrapper (ERR E4.2). At the LLVM level main /// returns i32. `tag_val` is the u32 error tag (0 = "no error"); `value` is /// the integer value slot for a value-carrying `-> (int, !)` main, or null @@ -4799,7 +4654,7 @@ pub const LLVMEmitter = struct { // Error: resolve the tag name, report to stderr, exit 1. c.LLVMPositionBuilderAtEnd(self.builder, err_bb); - const global = self.getOrBuildTagNameArray(); + const global = self.reflection().getOrBuildTagNameArray(); const idx = c.LLVMBuildZExt(self.builder, tag_i32, self.cached_i64, "main.tagidx"); const string_ty = self.getStringStructType(); const n: u32 = @intCast(self.ir_mod.types.tags.names.items.len); @@ -4822,44 +4677,6 @@ pub const LLVMEmitter = struct { _ = c.LLVMBuildRet(self.builder, c.LLVMConstInt(self.cached_i32, 1, 0)); } - /// The always-linked tag-name table: a `[N x {ptr, i64}]` global of tag - /// names indexed by global tag id (the `TagRegistry` namespace; slot 0 is - /// the reserved "" no-error name). `error_tag_name_get` GEPs into it at the - /// runtime tag id. Built once per module. Always emitted (not trace-gated) - /// so `{}` interpolation of an error tag works even in release builds. - fn getOrBuildTagNameArray(self: *LLVMEmitter) c.LLVMValueRef { - if (self.tag_name_array) |g| return g; - - const string_ty = self.getStringStructType(); - const names = self.ir_mod.types.tags.names.items; - - var field_vals = std.ArrayList(c.LLVMValueRef).empty; - defer field_vals.deinit(self.alloc); - for (names) |name_str| { - const str_z = self.alloc.dupeZ(u8, name_str) catch unreachable; - defer self.alloc.free(str_z); - const global_str = c.LLVMAddGlobal(self.llvm_module, c.LLVMArrayType(self.cached_i8, @intCast(name_str.len + 1)), "tag.str"); - c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.context, str_z.ptr, @intCast(name_str.len + 1), 1)); - c.LLVMSetGlobalConstant(global_str, 1); - c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage); - const len_val = c.LLVMConstInt(self.cached_i64, name_str.len, 0); - var struct_fields = [2]c.LLVMValueRef{ global_str, len_val }; - const const_struct = c.LLVMConstStructInContext(self.context, &struct_fields, 2, 0); - field_vals.append(self.alloc, const_struct) catch unreachable; - } - - const n: u32 = @intCast(names.len); - const array_ty = c.LLVMArrayType(string_ty, n); - const array_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n); - const global = c.LLVMAddGlobal(self.llvm_module, array_ty, "tag_names"); - c.LLVMSetInitializer(global, array_init); - c.LLVMSetGlobalConstant(global, 1); - c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); - - self.tag_name_array = global; - return global; - } - /// Emit field_value_get: switch on runtime index, each case extracts a field and boxes it as Any. fn emitFieldValueGet(self: *LLVMEmitter, fr: ir_inst.FieldReflect, func_idx: u32) void { const base_val = self.resolveRef(fr.base); From 91651e3e5619033abf147f7e1abe89acbb3ba190 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 09:39:16 +0300 Subject: [PATCH 06/26] test(backend): pin Obj-C property + alloc/dealloc IMP ctors before A7.3 extraction (A7.3 scaffolding) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend-FFI .ir inventory + scaffolding for the Obj-C/JNI runtime-constructor extraction (Phase A7.3). No code moved. Inventory (recorded in ARCH-SAFETY.md): the existing FFI .ir set already pins the core constructor emission — emitObjcSelectorInit (sel_registerName via 1309/1329/ 1332), emitObjcClassInit (objc_getClass), emitObjcDefinedClassInit class registration + ivars + method IMP table (objc_allocateClassPair / class_addIvar / class_addMethod / objc_registerClassPair via 1309/1332), and getOrCreateJniSlots / emitJniConstructor (GetMethodID via 1402/1418/1408). Gaps closed (2 new .ir snapshots) for the ARCH-SAFETY-named metadata not covered by 1309: - 1319-ffi-objc-property-sx-defined: property getter/setter IMPs (_get/_set/ class_addMethod x8). - 1314-ffi-objc-class-dealloc-roundtrip: alloc/dealloc IMPs. Both path-free + idempotent (verified across two captures; trailing newline trimmed). Suite count unchanged (snapshots on existing examples). Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (no churn beyond the 2 new .ir). --- .../1314-ffi-objc-class-dealloc-roundtrip.ir | 1147 +++++ .../1319-ffi-objc-property-sx-defined.ir | 4421 +++++++++++++++++ 2 files changed, 5568 insertions(+) create mode 100644 examples/expected/1314-ffi-objc-class-dealloc-roundtrip.ir create mode 100644 examples/expected/1319-ffi-objc-property-sx-defined.ir diff --git a/examples/expected/1314-ffi-objc-class-dealloc-roundtrip.ir b/examples/expected/1314-ffi-objc-class-dealloc-roundtrip.ir new file mode 100644 index 0000000..40da071 --- /dev/null +++ b/examples/expected/1314-ffi-objc-class-dealloc-roundtrip.ir @@ -0,0 +1,1147 @@ + +@__SxFoo_state_ivar = internal global ptr null +@__SxFoo_class = internal global ptr null +@OS = internal global i64 0 +@ARCH = internal global i64 0 +@POINTER_SIZE = internal global i64 8 +@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null } +@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00" +@str = private unnamed_addr constant [2 x i8] c"0\00", align 1 +@str.1 = private unnamed_addr constant [15 x i8] c"result := \22\22; \00", align 1 +@str.2 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.3 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.4 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.5 = private unnamed_addr constant [44 x i8] c"result = concat(result, any_to_string(args[\00", align 1 +@str.6 = private unnamed_addr constant [6 x i8] c"])); \00", align 1 +@str.7 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.8 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.9 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.10 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.11 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.12 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.13 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.14 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.15 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.16 = private unnamed_addr constant [6 x i8] c"SxFoo\00", align 1 +@str.17 = private unnamed_addr constant [8 x i8] c"dealloc\00", align 1 +@str.18 = private unnamed_addr constant [6 x i8] c"alloc\00", align 1 +@str.19 = private unnamed_addr constant [8 x i8] c"release\00", align 1 +@str.20 = private unnamed_addr constant [28 x i8] c"FAIL: SxFoo not registered\0A\00", align 1 +@str.21 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.22 = private unnamed_addr constant [28 x i8] c"FAIL: SxFoo not registered\0A\00", align 1 +@str.23 = private unnamed_addr constant [27 x i8] c"FAIL: dealloc IMP missing\0A\00", align 1 +@str.24 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.25 = private unnamed_addr constant [27 x i8] c"FAIL: dealloc IMP missing\0A\00", align 1 +@str.26 = private unnamed_addr constant [28 x i8] c"FAIL: +alloc returned null\0A\00", align 1 +@str.27 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.28 = private unnamed_addr constant [28 x i8] c"FAIL: +alloc returned null\0A\00", align 1 +@str.29 = private unnamed_addr constant [36 x i8] c"FAIL: +alloc round 2 returned null\0A\00", align 1 +@str.30 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.31 = private unnamed_addr constant [36 x i8] c"FAIL: +alloc round 2 returned null\0A\00", align 1 +@str.32 = private unnamed_addr constant [13 x i8] c"dealloc: ok\0A\00", align 1 +@str.33 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.34 = private unnamed_addr constant [13 x i8] c"dealloc: ok\0A\00", align 1 +@OBJC_IVAR_NAME_ = private unnamed_addr constant [11 x i8] c"__sx_state\00" +@OBJC_IVAR_TYPE_ = private unnamed_addr constant [3 x i8] c"^v\00" +@OBJC_CLASS_NAME_ = private unnamed_addr constant [9 x i8] c"NSObject\00" +@OBJC_CLASS_NAME_.35 = private unnamed_addr constant [6 x i8] c"SxFoo\00" +@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [5 x i8] c"bump\00" +@OBJC_METH_VAR_TYPE_ = private unnamed_addr constant [4 x i8] c"v@:\00" +@OBJC_METH_VAR_NAME_.36 = private unnamed_addr constant [8 x i8] c"dealloc\00" +@OBJC_METH_VAR_TYPE_.37 = private unnamed_addr constant [4 x i8] c"v@:\00" +@OBJC_METH_VAR_NAME_.38 = private unnamed_addr constant [6 x i8] c"alloc\00" +@OBJC_METH_VAR_TYPE_.39 = private unnamed_addr constant [4 x i8] c"@@:\00" + +; Function Attrs: nounwind +declare void @out(ptr) #0 + +declare ptr @malloc(i64) + +declare void @free(ptr) + +declare ptr @memcpy(ptr, ptr, i64) + +declare ptr @memset(ptr, i32, i64) + +; Function Attrs: nounwind +define internal ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %2, ptr %allocaN, align 8 + %load = load i64, ptr %allocaN, align 8 + %call = call ptr @malloc(i64 %load) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca ptr, align 8 + store ptr %2, ptr %allocaN, align 8 + %load = load ptr, ptr %allocaN, align 8 + call void @free(ptr %load) + ret void +} + +; Function Attrs: nounwind +declare i64 @GPA.init(ptr) #0 + +; Function Attrs: nounwind +declare ptr @GPA.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @GPA.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.add_chunk(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.init(ptr sret({ ptr, i64, { ptr, ptr, ptr } }), ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.deinit(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @Arena.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.init(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.init(ptr sret({ { ptr, ptr, ptr }, i64, i64, i64 }), ptr, ptr) #0 + +; Function Attrs: nounwind +declare i64 @TrackingAllocator.leak_count(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.report(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @TrackingAllocator.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 { +entry: + %alloca = alloca i64, align 8 + store i64 %1, ptr %alloca, align 8 + %load = load i64, ptr %alloca, align 8 + %add = add i64 %load, 1 + %loadN = load { { ptr, ptr, ptr }, ptr }, ptr %0, align 8 + %sg = extractvalue { { ptr, ptr, ptr }, ptr } %loadN, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 1 + %icall = call ptr %sgN(ptr %0, ptr %sgN, i64 %add) + %allocaN = alloca ptr, align 8 + store ptr %icall, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load i64, ptr %alloca, align 8 + %addN = add i64 %loadN, 1 + %2 = call ptr @memset(ptr %loadN, i32 0, i64 %addN) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } undef, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + %pti = ptrtoint ptr %loadN to i64 + store i64 %pti, ptr %gep, align 8 + %loadN = load i64, ptr %alloca, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 %loadN, ptr %gepN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @int_to_string(ptr %0, i64 %1) #0 { +entry: + %alloca = alloca i64, align 8 + store i64 %1, ptr %alloca, align 8 + %load = load i64, ptr %alloca, align 8 + %icmp = icmp eq i64 %load, 0 + br i1 %icmp, label %if.then.16, label %if.merge.17 + +if.then.16: ; preds = %entry + ret { ptr, i64 } { ptr @str, i64 1 } + +if.merge.17: ; preds = %entry + %loadN = load i64, ptr %alloca, align 8 + %icmpN = icmp slt i64 %loadN, 0 + %allocaN = alloca i1, align 1 + store i1 %icmpN, ptr %allocaN, align 1 + %loadN = load i1, ptr %allocaN, align 1 + br i1 %loadN, label %if.then.18, label %if.else.19 + +if.then.18: ; preds = %if.merge.17 + %loadN = load i64, ptr %alloca, align 8 + %sub = sub i64 0, %loadN + br label %if.merge.20 + +if.else.19: ; preds = %if.merge.17 + %loadN = load i64, ptr %alloca, align 8 + br label %if.merge.20 + +if.merge.20: ; preds = %if.else.19, %if.then.18 + %bp = phi i64 [ %sub, %if.then.18 ], [ %loadN, %if.else.19 ] + %allocaN = alloca i64, align 8 + store i64 %bp, ptr %allocaN, align 8 + %call = call { ptr, i64 } @cstring(ptr %0, i64 20) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 19, ptr %allocaN, align 8 + br label %while.hdr.21 + +while.hdr.21: ; preds = %while.body.22, %if.merge.20 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %while.body.22, label %while.exit.23 + +while.body.22: ; preds = %while.hdr.21 + %loadN = load i64, ptr %allocaN, align 8 + %srem = srem i64 %loadN, 10 + %add = add i64 %srem, 48 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN + %trunc = trunc i64 %add to i8 + store i8 %trunc, ptr %igp.ptr, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %sdiv = sdiv i64 %loadN, 10 + store i64 %sdiv, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, 1 + store i64 %subN, ptr %allocaN, align 8 + br label %while.hdr.21 + +while.exit.23: ; preds = %while.hdr.21 + %loadN = load i1, ptr %allocaN, align 1 + br i1 %loadN, label %if.then.24, label %if.merge.25 + +if.then.24: ; preds = %while.exit.23 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data21 = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr22 = getelementptr i8, ptr %igp.data21, i64 %loadN + store i8 45, ptr %igp.ptr22, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, 1 + store i64 %subN, ptr %allocaN, align 8 + br label %if.merge.25 + +if.merge.25: ; preds = %if.then.24, %while.exit.23 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 20, %loadN + %subN = sub i64 %subN, 1 + %callN = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 %addN, i64 %subN) + ret { ptr, i64 } %callN +} + +; Function Attrs: nounwind +declare ptr @bool_to_string(ptr, i1) #0 + +; Function Attrs: nounwind +declare ptr @float_to_string(ptr, double) #0 + +; Function Attrs: nounwind +declare void @hex_group(ptr, ptr, i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @int_to_hex_string(ptr, i64) #0 + +; Function Attrs: nounwind +define internal { ptr, i64 } @concat(ptr %0, { ptr, i64 } %1, { ptr, i64 } %2) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %2, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %alloca, align 8 + %len = extractvalue { ptr, i64 } %load, 1 + %allocaN = alloca i64, align 8 + store i64 %len, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %allocaN = alloca i64, align 8 + store i64 %lenN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, %loadN + %call = call { ptr, i64 } @cstring(ptr %0, i64 %add) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptr = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %dptrN = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN) + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptrN = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @substr(ptr %0, { ptr, i64 } %1, i64 %2, i64 %3) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %2, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 %3, ptr %allocaN, align 8 + %load = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @cstring(ptr %0, i64 %load) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptr = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +declare ptr @xml_escape(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @path_join(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @any_to_string(ptr, [2 x i64]) #0 + +; Function Attrs: nounwind +define internal { ptr, i64 } @build_format(ptr %0, { ptr, i64 } %1) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.1, i64 14 }, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.2 + +while.hdr.2: ; preds = %if.merge.7, %entry + %load = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %len = extractvalue { ptr, i64 } %loadN, 1 + %icmp = icmp slt i64 %load, %len + br i1 %icmp, label %while.body.3, label %while.exit.4 + +while.body.3: ; preds = %while.hdr.2 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.data = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr = getelementptr i8, ptr %ig.data, i64 %loadN + %ig.val = load i8, ptr %ig.ptr, align 1 + %cmp.ext = zext i8 %ig.val to i64 + %icmpN = icmp eq i64 %cmp.ext, 123 + br i1 %icmpN, label %if.then.5, label %if.else.6 + +while.exit.4: ; preds = %while.hdr.2 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %icmpN = icmp slt i64 %loadN, %lenN + br i1 %icmpN, label %if.then.38, label %if.merge.39 + +if.then.5: ; preds = %while.body.3 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %icmpN = icmp slt i64 %add, %lenN + br i1 %icmpN, label %if.then.8, label %if.else.9 + +if.else.6: ; preds = %while.body.3 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.data19 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr20 = getelementptr i8, ptr %ig.data19, i64 %loadN + %ig.val21 = load i8, ptr %ig.ptr20, align 1 + %cmp.ext22 = zext i8 %ig.val21 to i64 + %icmpN = icmp eq i64 %cmp.ext22, 125 + br i1 %icmpN, label %if.then.29, label %if.else.30 + +if.merge.7: ; preds = %if.merge.31, %if.merge.10 + br label %while.hdr.2 + +if.then.8: ; preds = %if.then.5 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %ig.data27 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr28 = getelementptr i8, ptr %ig.data27, i64 %addN + %ig.val29 = load i8, ptr %ig.ptr28, align 1 + %cmp.ext30 = zext i8 %ig.val29 to i64 + %icmpN = icmp eq i64 %cmp.ext30, 125 + br i1 %icmpN, label %if.then.11, label %if.else.12 + +if.else.9: ; preds = %if.then.5 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.10 + +if.merge.10: ; preds = %if.merge.13, %if.else.9 + br label %if.merge.7 + +if.then.11: ; preds = %if.then.8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, %loadN + br i1 %icmpN, label %if.then.14, label %if.merge.15 + +if.else.12: ; preds = %if.then.8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %ig.data40 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr41 = getelementptr i8, ptr %ig.data40, i64 %addN + %ig.val42 = load i8, ptr %ig.ptr41, align 1 + %cmp.ext43 = zext i8 %ig.val42 to i64 + %icmpN = icmp eq i64 %cmp.ext43, 123 + br i1 %icmpN, label %if.then.26, label %if.else.27 + +if.merge.13: ; preds = %if.merge.28, %if.merge.15 + br label %if.merge.10 + +if.then.14: ; preds = %if.then.11 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.2, i64 36 }) + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.3, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %sub = sub i64 %loadN, %loadN + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %sub) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.4, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.15 + +if.merge.15: ; preds = %if.then.14, %if.then.11 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.5, i64 43 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.6, i64 5 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 2 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + store i64 %loadN, ptr %allocaN, align 8 + br label %if.merge.13 + +if.then.26: ; preds = %if.else.12 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.7, i64 36 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.8, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, %loadN + %addN = add i64 %subN, 1 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %addN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.9, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 2 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + store i64 %loadN, ptr %allocaN, align 8 + br label %if.merge.28 + +if.else.27: ; preds = %if.else.12 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.28 + +if.merge.28: ; preds = %if.else.27, %if.then.26 + br label %if.merge.13 + +if.then.29: ; preds = %if.else.6 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %icmpN = icmp slt i64 %addN, %lenN + br i1 %icmpN, label %if.then.32, label %if.else.33 + +if.else.30: ; preds = %if.else.6 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.31 + +if.merge.31: ; preds = %if.merge.34, %if.else.30 + br label %if.merge.7 + +if.then.32: ; preds = %if.then.29 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %ig.data104 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr105 = getelementptr i8, ptr %ig.data104, i64 %addN + %ig.val106 = load i8, ptr %ig.ptr105, align 1 + %cmp.ext107 = zext i8 %ig.val106 to i64 + %icmpN = icmp eq i64 %cmp.ext107, 125 + br i1 %icmpN, label %if.then.35, label %if.else.36 + +if.else.33: ; preds = %if.then.29 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.34 + +if.merge.34: ; preds = %if.merge.37, %if.else.33 + br label %if.merge.31 + +if.then.35: ; preds = %if.then.32 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.10, i64 36 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.11, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, %loadN + %addN = add i64 %subN, 1 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %addN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.12, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 2 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + store i64 %loadN, ptr %allocaN, align 8 + br label %if.merge.37 + +if.else.36: ; preds = %if.then.32 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.37 + +if.merge.37: ; preds = %if.else.36, %if.then.35 + br label %if.merge.34 + +if.then.38: ; preds = %while.exit.4 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.13, i64 36 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.14, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %lenN, %loadN + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %subN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.15, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.39 + +if.merge.39: ; preds = %if.then.38, %while.exit.4 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +declare void @BuildOptions.add_link_flag(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.add_framework(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_output_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_wasm_shell(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.add_asset_dir(i64, ptr, ptr) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.asset_dir_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.asset_dir_src_at(i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.asset_dir_dest_at(i64, i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_post_link_callback(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_post_link_module(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.binary_path(i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_bundle_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_bundle_id(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_codesign_identity(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_provisioning_profile(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.bundle_path(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.bundle_id(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.codesign_identity(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.provisioning_profile(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.target_triple(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_macos(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios_device(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios_simulator(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_android(i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.framework_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.framework_at(i64, i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.framework_path_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.framework_path_at(i64, i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_manifest_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_keystore_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.manifest_path(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.keystore_path(i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.jni_main_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.jni_main_foreign_path_at(i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0 + +; Function Attrs: nounwind +declare i64 @build_options() #0 + +; Function Attrs: nounwind +declare ptr @objc_getClass(ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_lookUpClass(ptr) #0 + +; Function Attrs: nounwind +declare ptr @sel_registerName(ptr) #0 + +; Function Attrs: nounwind +declare ptr @class_createInstance(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @object_getClass(ptr) #0 + +; Function Attrs: nounwind +declare ptr @object_getIvar(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @object_setIvar(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_msgSend(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_allocateClassPair(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare i1 @class_addMethod(ptr, ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare i1 @class_addProtocol(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_getProtocol(ptr) #0 + +; Function Attrs: nounwind +declare void @objc_registerClassPair(ptr) #0 + +; Function Attrs: nounwind +declare void @NSLog(ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_autoreleasePoolPush() #0 + +; Function Attrs: nounwind +declare void @objc_autoreleasePoolPop(ptr) #0 + +; Function Attrs: nounwind +declare void @autoreleasepool(ptr, [2 x i64]) #0 + +; Function Attrs: nounwind +declare ptr @class_getInstanceVariable(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @class_getMethodImplementation(ptr, ptr) #0 + +; Function Attrs: nounwind +define internal void @SxFoo.bump(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %load = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr @__SxFoo_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %load, ptr %loadN) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32 }, ptr %call, i32 0, i32 1 + %loadN = load i32, ptr %gep, align 4 + %add = add i32 %loadN, 1 + store i32 %add, ptr %gep, align 4 + ret void +} + +; Function Attrs: nounwind +define i32 @main() #0 { +entry: + call void @__sx_objc_defined_class_init() + %alloca = alloca ptr, align 8 + %call = call ptr @objc_getClass(ptr @str.16) + store ptr %call, ptr %alloca, align 8 + %load = load ptr, ptr %alloca, align 8 + %icmp = icmp eq ptr %load, null + br i1 %icmp, label %if.then.0, label %if.merge.1 + +if.then.0: ; preds = %entry + call void @print__ct_s354c93d7643e1bdf__pack(ptr @__sx_default_context) + ret i32 1 + +if.merge.1: ; preds = %entry + %allocaN = alloca ptr, align 8 + %callN = call ptr @sel_registerName(ptr @str.17) + store ptr %callN, ptr %allocaN, align 8 + %allocaN = alloca ptr, align 8 + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %callN = call ptr @class_getMethodImplementation(ptr %loadN, ptr %loadN) + store ptr %callN, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %icmpN = icmp eq ptr %loadN, null + br i1 %icmpN, label %if.then.40, label %if.merge.41 + +if.then.40: ; preds = %if.merge.1 + call void @print__ct_sfe783e2b27a4beff__pack(ptr @__sx_default_context) + ret i32 1 + +if.merge.41: ; preds = %if.merge.1 + %allocaN = alloca ptr, align 8 + %callN = call ptr @sel_registerName(ptr @str.18) + store ptr %callN, ptr %allocaN, align 8 + %allocaN = alloca ptr, align 8 + store ptr @objc_msgSend, ptr %allocaN, align 8 + %allocaN = alloca ptr, align 8 + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %icall = call ptr %loadN(ptr %loadN, ptr %loadN) + store ptr %icall, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %icmpN = icmp eq ptr %loadN, null + br i1 %icmpN, label %if.then.42, label %if.merge.43 + +if.then.42: ; preds = %if.merge.41 + call void @print__ct_scaebdbbd10c81716__pack(ptr @__sx_default_context) + ret i32 1 + +if.merge.43: ; preds = %if.merge.41 + %allocaN = alloca ptr, align 8 + %callN = call ptr @sel_registerName(ptr @str.19) + store ptr %callN, ptr %allocaN, align 8 + %allocaN = alloca ptr, align 8 + store ptr @objc_msgSend, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + call void %loadN(ptr %loadN, ptr %loadN) + %allocaN = alloca ptr, align 8 + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %icallN = call ptr %loadN(ptr %loadN, ptr %loadN) + store ptr %icallN, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %icmpN = icmp eq ptr %loadN, null + br i1 %icmpN, label %if.then.44, label %if.merge.45 + +if.then.44: ; preds = %if.merge.43 + call void @print__ct_s7c1052877b8cc801__pack(ptr @__sx_default_context) + ret i32 1 + +if.merge.45: ; preds = %if.merge.43 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + call void %loadN(ptr %loadN, ptr %loadN) + call void @print__ct_sed4e79fbcbd67966__pack(ptr @__sx_default_context) + ret i32 0 +} + +; Function Attrs: nounwind +define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %call = call ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + call void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) + ret void +} + +; Function Attrs: nounwind +define internal void @print__ct_s354c93d7643e1bdf__pack(ptr %0) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.20, i64 27 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr null, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 0, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.21, i64 0 }, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 27) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %load, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %1 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_0(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.22, i64 27 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal void @print__ct_sfe783e2b27a4beff__pack(ptr %0) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.23, i64 26 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr null, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 0, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.24, i64 0 }, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 26) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %load, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %1 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_1(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.25, i64 26 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal void @print__ct_scaebdbbd10c81716__pack(ptr %0) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.26, i64 27 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr null, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 0, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.27, i64 0 }, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 27) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %load, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %1 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_2(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.28, i64 27 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal void @print__ct_s7c1052877b8cc801__pack(ptr %0) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.29, i64 35 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr null, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 0, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.30, i64 0 }, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 35) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %load, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %1 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_3(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.31, i64 35 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal void @print__ct_sed4e79fbcbd67966__pack(ptr %0) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.32, i64 12 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr null, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 0, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.33, i64 0 }, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 12) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %load, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %1 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_4(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.34, i64 12 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define ptr @__SxFoo_alloc_imp(ptr %0, ptr %1) #0 { +entry: + %call = call ptr @class_createInstance(ptr %0, i64 0) + %load = load { { ptr, ptr, ptr }, ptr }, ptr @__sx_default_context, align 8 + %sg = extractvalue { { ptr, ptr, ptr }, ptr } %load, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 1 + %icall = call ptr %sgN(ptr @__sx_default_context, ptr %sgN, i64 32) + %callN = call ptr @memset(ptr %icall, i32 0, i64 32) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32 }, ptr %icall, i32 0, i32 0 + store { ptr, ptr, ptr } %sg, ptr %gep, align 8 + %loadN = load ptr, ptr @__SxFoo_state_ivar, align 8 + call void @object_setIvar(ptr %call, ptr %loadN, ptr %icall) + ret ptr %call +} + +; Function Attrs: nounwind +define void @__SxFoo_dealloc_imp(ptr %0, ptr %1) #0 { +entry: + %load = load ptr, ptr @__SxFoo_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %0, ptr %load) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32 }, ptr %call, i32 0, i32 0 + %loadN = load { ptr, ptr, ptr }, ptr %gep, align 8 + %sg = extractvalue { ptr, ptr, ptr } %loadN, 0 + %sgN = extractvalue { ptr, ptr, ptr } %loadN, 2 + call void %sgN(ptr @__sx_default_context, ptr %sg, ptr %call) + call void @object_setIvar(ptr %0, ptr %load, ptr null) + %alloca = alloca { ptr, ptr }, align 8 + %gepN = getelementptr inbounds { ptr, ptr }, ptr %alloca, i32 0, i32 0 + store ptr %0, ptr %gepN, align 8 + %loadN = load ptr, ptr @__SxFoo_class, align 8 + %gepN = getelementptr inbounds { ptr, ptr }, ptr %alloca, i32 0, i32 1 + store ptr %loadN, ptr %gepN, align 8 + %callN = call ptr @sel_registerName(ptr @__sx_objc_cstr_dealloc) + call void @objc_msgSendSuper2(ptr %alloca, ptr %callN) + ret void +} + +; Function Attrs: nounwind +declare void @objc_msgSendSuper2(ptr, ptr) #0 + +; Function Attrs: nounwind +define void @__SxFoo_bump_imp(ptr %0, ptr %1) #0 { +entry: + call void @SxFoo.bump(ptr @__sx_default_context, ptr %0) + ret void +} + +declare i64 @write(i32, ptr, i64) + +declare i8 @class_addIvar(ptr, ptr, i64, i8, ptr) + +define internal void @__sx_objc_defined_class_init() { +entry: + %super_cls = call ptr @objc_getClass(ptr @OBJC_CLASS_NAME_) + %cls = call ptr @objc_allocateClassPair(ptr %super_cls, ptr @OBJC_CLASS_NAME_.35, i64 0) + %0 = call i8 @class_addIvar(ptr %cls, ptr @OBJC_IVAR_NAME_, i64 8, i8 3, ptr @OBJC_IVAR_TYPE_) + %metacls = call ptr @object_getClass(ptr %cls) + %sel = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_) + %1 = call i1 @class_addMethod(ptr %cls, ptr %sel, ptr @__SxFoo_bump_imp, ptr @OBJC_METH_VAR_TYPE_) + call void @objc_registerClassPair(ptr %cls) + store ptr %cls, ptr @__SxFoo_class, align 8 + %sel_dealloc = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.36) + %2 = call i1 @class_addMethod(ptr %cls, ptr %sel_dealloc, ptr @__SxFoo_dealloc_imp, ptr @OBJC_METH_VAR_TYPE_.37) + %sel_alloc = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.38) + %3 = call i1 @class_addMethod(ptr %metacls, ptr %sel_alloc, ptr @__SxFoo_alloc_imp, ptr @OBJC_METH_VAR_TYPE_.39) + %iv = call ptr @class_getInstanceVariable(ptr %cls, ptr @OBJC_IVAR_NAME_) + store ptr %iv, ptr @__SxFoo_state_ivar, align 8 + ret void +} diff --git a/examples/expected/1319-ffi-objc-property-sx-defined.ir b/examples/expected/1319-ffi-objc-property-sx-defined.ir new file mode 100644 index 0000000..4e94c05 --- /dev/null +++ b/examples/expected/1319-ffi-objc-property-sx-defined.ir @@ -0,0 +1,4421 @@ + +@__SxBox_state_ivar = internal global ptr null +@__SxBox_class = internal global ptr null +@OS = internal global i64 0 +@ARCH = internal global i64 0 +@POINTER_SIZE = internal global i64 8 +@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null } +@OBJC_CLASSLIST_REFERENCES_SxBox = internal global ptr null +@OBJC_SELECTOR_REFERENCES_init = internal global ptr null +@OBJC_SELECTOR_REFERENCES_setWidth_ = internal global ptr null +@OBJC_SELECTOR_REFERENCES_setHeight_ = internal global ptr null +@OBJC_SELECTOR_REFERENCES_width = internal global ptr null +@OBJC_SELECTOR_REFERENCES_height = internal global ptr null +@OBJC_SELECTOR_REFERENCES_area = internal global ptr null +@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00" +@str = private unnamed_addr constant [2 x i8] c"0\00", align 1 +@str.1 = private unnamed_addr constant [5 x i8] c"true\00", align 1 +@str.2 = private unnamed_addr constant [6 x i8] c"false\00", align 1 +@str.3 = private unnamed_addr constant [2 x i8] c"0\00", align 1 +@str.4 = private unnamed_addr constant [4 x i8] c"\00", align 1 +@str.5 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@tn.str = private constant [13 x i8] c"\00" +@tn.str.6 = private constant [5 x i8] c"bool\00" +@tn.str.7 = private constant [3 x i8] c"s8\00" +@tn.str.8 = private constant [4 x i8] c"s16\00" +@tn.str.9 = private constant [4 x i8] c"s32\00" +@tn.str.10 = private constant [4 x i8] c"s64\00" +@tn.str.11 = private constant [3 x i8] c"u8\00" +@tn.str.12 = private constant [4 x i8] c"u16\00" +@tn.str.13 = private constant [4 x i8] c"u32\00" +@tn.str.14 = private constant [4 x i8] c"u64\00" +@tn.str.15 = private constant [4 x i8] c"f32\00" +@tn.str.16 = private constant [4 x i8] c"f64\00" +@tn.str.17 = private constant [7 x i8] c"string\00" +@tn.str.18 = private constant [4 x i8] c"Any\00" +@tn.str.19 = private constant [9 x i8] c"noreturn\00" +@tn.str.20 = private constant [6 x i8] c"isize\00" +@tn.str.21 = private constant [6 x i8] c"usize\00" +@tn.str.22 = private constant [5 x i8] c"void\00" +@tn.str.23 = private constant [6 x i8] c"*void\00" +@tn.str.24 = private constant [16 x i8] c"Source_Location\00" +@tn.str.25 = private constant [10 x i8] c"Allocator\00" +@tn.str.26 = private constant [11 x i8] c"CAllocator\00" +@tn.str.27 = private constant [12 x i8] c"*CAllocator\00" +@tn.str.28 = private constant [4 x i8] c"GPA\00" +@tn.str.29 = private constant [5 x i8] c"*GPA\00" +@tn.str.30 = private constant [11 x i8] c"ArenaChunk\00" +@tn.str.31 = private constant [12 x i8] c"*ArenaChunk\00" +@tn.str.32 = private constant [6 x i8] c"Arena\00" +@tn.str.33 = private constant [7 x i8] c"*Arena\00" +@tn.str.34 = private constant [6 x i8] c"[*]u8\00" +@tn.str.35 = private constant [9 x i8] c"BufAlloc\00" +@tn.str.36 = private constant [10 x i8] c"*BufAlloc\00" +@tn.str.37 = private constant [18 x i8] c"TrackingAllocator\00" +@tn.str.38 = private constant [19 x i8] c"*TrackingAllocator\00" +@tn.str.39 = private constant [8 x i8] c"Context\00" +@tn.str.40 = private constant [9 x i8] c"[]string\00" +@tn.str.41 = private constant [16 x i8] c"OperatingSystem\00" +@tn.str.42 = private constant [13 x i8] c"Architecture\00" +@tn.str.43 = private constant [13 x i8] c"BuildOptions\00" +@tn.str.44 = private constant [11 x i8] c"() -> bool\00" +@tn.str.45 = private constant [9 x i8] c"NSString\00" +@tn.str.46 = private constant [10 x i8] c"*NSString\00" +@tn.str.47 = private constant [10 x i8] c"Closure()\00" +@tn.str.48 = private constant [13 x i8] c"**CAllocator\00" +@tn.str.49 = private constant [5 x i8] c"*s64\00" +@tn.str.50 = private constant [7 x i8] c"**void\00" +@tn.str.51 = private constant [6 x i8] c"SxBox\00" +@tn.str.52 = private constant [7 x i8] c"*SxBox\00" +@tn.str.53 = private constant [13 x i8] c"__SxBoxState\00" +@tn.str.54 = private constant [8 x i8] c"**SxBox\00" +@tn.str.55 = private constant [5 x i8] c"*s32\00" +@tn.str.56 = private constant [8 x i8] c"*string\00" +@tn.str.57 = private constant [6 x i8] c"[]Any\00" +@tn.str.58 = private constant [5 x i8] c"*Any\00" +@tn.str.59 = private constant [7 x i8] c"*[]Any\00" +@tn.str.60 = private constant [4 x i8] c"*u8\00" +@tn.str.61 = private constant [6 x i8] c"*bool\00" +@tn.str.62 = private constant [7 x i8] c"[1]Any\00" +@tn.str.63 = private constant [8 x i8] c"*[1]Any\00" +@tn.str.64 = private constant [7 x i8] c"[3]Any\00" +@tn.str.65 = private constant [8 x i8] c"*[3]Any\00" +@tn.str.66 = private constant [5 x i8] c"*f64\00" +@tn.str.67 = private constant [17 x i8] c"*Source_Location\00" +@tn.str.68 = private constant [11 x i8] c"*Allocator\00" +@tn.str.69 = private constant [9 x i8] c"*Context\00" +@tn.str.70 = private constant [14 x i8] c"*BuildOptions\00" +@tn.str.71 = private constant [14 x i8] c"*__SxBoxState\00" +@tn.str.72 = private constant [17 x i8] c"*OperatingSystem\00" +@tn.str.73 = private constant [14 x i8] c"*Architecture\00" +@tn.str.74 = private constant [10 x i8] c"*[]string\00" +@tn.str.75 = private constant [6 x i8] c"**GPA\00" +@tn.str.76 = private constant [13 x i8] c"**ArenaChunk\00" +@tn.str.77 = private constant [8 x i8] c"**Arena\00" +@tn.str.78 = private constant [7 x i8] c"*[*]u8\00" +@tn.str.79 = private constant [11 x i8] c"**BufAlloc\00" +@tn.str.80 = private constant [20 x i8] c"**TrackingAllocator\00" +@tn.str.81 = private constant [11 x i8] c"**NSString\00" +@tn.str.82 = private constant [14 x i8] c"***CAllocator\00" +@tn.str.83 = private constant [6 x i8] c"**s64\00" +@tn.str.84 = private constant [8 x i8] c"***void\00" +@tn.str.85 = private constant [9 x i8] c"***SxBox\00" +@tn.str.86 = private constant [6 x i8] c"**s32\00" +@tn.str.87 = private constant [9 x i8] c"**string\00" +@tn.str.88 = private constant [6 x i8] c"**Any\00" +@tn.str.89 = private constant [8 x i8] c"**[]Any\00" +@tn.str.90 = private constant [5 x i8] c"**u8\00" +@tn.str.91 = private constant [7 x i8] c"**bool\00" +@tn.str.92 = private constant [9 x i8] c"**[1]Any\00" +@tn.str.93 = private constant [9 x i8] c"**[3]Any\00" +@tn.str.94 = private constant [16 x i8] c"__sx_objc_super\00" +@tn.str.95 = private constant [17 x i8] c"*__sx_objc_super\00" +@tn.str.96 = private constant [6 x i8] c"[8]u8\00" +@__sx_type_names = private constant [92 x { ptr, i64 }] [{ ptr, i64 } { ptr @tn.str, i64 12 }, { ptr, i64 } { ptr @tn.str.6, i64 4 }, { ptr, i64 } { ptr @tn.str.7, i64 2 }, { ptr, i64 } { ptr @tn.str.8, i64 3 }, { ptr, i64 } { ptr @tn.str.9, i64 3 }, { ptr, i64 } { ptr @tn.str.10, i64 3 }, { ptr, i64 } { ptr @tn.str.11, i64 2 }, { ptr, i64 } { ptr @tn.str.12, i64 3 }, { ptr, i64 } { ptr @tn.str.13, i64 3 }, { ptr, i64 } { ptr @tn.str.14, i64 3 }, { ptr, i64 } { ptr @tn.str.15, i64 3 }, { ptr, i64 } { ptr @tn.str.16, i64 3 }, { ptr, i64 } { ptr @tn.str.17, i64 6 }, { ptr, i64 } { ptr @tn.str.18, i64 3 }, { ptr, i64 } { ptr @tn.str.19, i64 8 }, { ptr, i64 } { ptr @tn.str.20, i64 5 }, { ptr, i64 } { ptr @tn.str.21, i64 5 }, { ptr, i64 } { ptr @tn.str.22, i64 4 }, { ptr, i64 } { ptr @tn.str.23, i64 5 }, { ptr, i64 } { ptr @tn.str.24, i64 15 }, { ptr, i64 } { ptr @tn.str.25, i64 9 }, { ptr, i64 } { ptr @tn.str.26, i64 10 }, { ptr, i64 } { ptr @tn.str.27, i64 11 }, { ptr, i64 } { ptr @tn.str.28, i64 3 }, { ptr, i64 } { ptr @tn.str.29, i64 4 }, { ptr, i64 } { ptr @tn.str.30, i64 10 }, { ptr, i64 } { ptr @tn.str.31, i64 11 }, { ptr, i64 } { ptr @tn.str.32, i64 5 }, { ptr, i64 } { ptr @tn.str.33, i64 6 }, { ptr, i64 } { ptr @tn.str.34, i64 5 }, { ptr, i64 } { ptr @tn.str.35, i64 8 }, { ptr, i64 } { ptr @tn.str.36, i64 9 }, { ptr, i64 } { ptr @tn.str.37, i64 17 }, { ptr, i64 } { ptr @tn.str.38, i64 18 }, { ptr, i64 } { ptr @tn.str.39, i64 7 }, { ptr, i64 } { ptr @tn.str.40, i64 8 }, { ptr, i64 } { ptr @tn.str.41, i64 15 }, { ptr, i64 } { ptr @tn.str.42, i64 12 }, { ptr, i64 } { ptr @tn.str.43, i64 12 }, { ptr, i64 } { ptr @tn.str.44, i64 10 }, { ptr, i64 } { ptr @tn.str.45, i64 8 }, { ptr, i64 } { ptr @tn.str.46, i64 9 }, { ptr, i64 } { ptr @tn.str.47, i64 9 }, { ptr, i64 } { ptr @tn.str.48, i64 12 }, { ptr, i64 } { ptr @tn.str.49, i64 4 }, { ptr, i64 } { ptr @tn.str.50, i64 6 }, { ptr, i64 } { ptr @tn.str.51, i64 5 }, { ptr, i64 } { ptr @tn.str.52, i64 6 }, { ptr, i64 } { ptr @tn.str.53, i64 12 }, { ptr, i64 } { ptr @tn.str.54, i64 7 }, { ptr, i64 } { ptr @tn.str.55, i64 4 }, { ptr, i64 } { ptr @tn.str.56, i64 7 }, { ptr, i64 } { ptr @tn.str.57, i64 5 }, { ptr, i64 } { ptr @tn.str.58, i64 4 }, { ptr, i64 } { ptr @tn.str.59, i64 6 }, { ptr, i64 } { ptr @tn.str.60, i64 3 }, { ptr, i64 } { ptr @tn.str.61, i64 5 }, { ptr, i64 } { ptr @tn.str.62, i64 6 }, { ptr, i64 } { ptr @tn.str.63, i64 7 }, { ptr, i64 } { ptr @tn.str.64, i64 6 }, { ptr, i64 } { ptr @tn.str.65, i64 7 }, { ptr, i64 } { ptr @tn.str.66, i64 4 }, { ptr, i64 } { ptr @tn.str.67, i64 16 }, { ptr, i64 } { ptr @tn.str.68, i64 10 }, { ptr, i64 } { ptr @tn.str.69, i64 8 }, { ptr, i64 } { ptr @tn.str.70, i64 13 }, { ptr, i64 } { ptr @tn.str.71, i64 13 }, { ptr, i64 } { ptr @tn.str.72, i64 16 }, { ptr, i64 } { ptr @tn.str.73, i64 13 }, { ptr, i64 } { ptr @tn.str.74, i64 9 }, { ptr, i64 } { ptr @tn.str.75, i64 5 }, { ptr, i64 } { ptr @tn.str.76, i64 12 }, { ptr, i64 } { ptr @tn.str.77, i64 7 }, { ptr, i64 } { ptr @tn.str.78, i64 6 }, { ptr, i64 } { ptr @tn.str.79, i64 10 }, { ptr, i64 } { ptr @tn.str.80, i64 19 }, { ptr, i64 } { ptr @tn.str.81, i64 10 }, { ptr, i64 } { ptr @tn.str.82, i64 13 }, { ptr, i64 } { ptr @tn.str.83, i64 5 }, { ptr, i64 } { ptr @tn.str.84, i64 7 }, { ptr, i64 } { ptr @tn.str.85, i64 8 }, { ptr, i64 } { ptr @tn.str.86, i64 5 }, { ptr, i64 } { ptr @tn.str.87, i64 8 }, { ptr, i64 } { ptr @tn.str.88, i64 5 }, { ptr, i64 } { ptr @tn.str.89, i64 7 }, { ptr, i64 } { ptr @tn.str.90, i64 4 }, { ptr, i64 } { ptr @tn.str.91, i64 6 }, { ptr, i64 } { ptr @tn.str.92, i64 8 }, { ptr, i64 } { ptr @tn.str.93, i64 8 }, { ptr, i64 } { ptr @tn.str.94, i64 15 }, { ptr, i64 } { ptr @tn.str.95, i64 16 }, { ptr, i64 } { ptr @tn.str.96, i64 5 }] +@str.97 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.98 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.99 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.100 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.101 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.102 = private unnamed_addr constant [15 x i8] c"result := \22\22; \00", align 1 +@str.103 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.104 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.105 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.106 = private unnamed_addr constant [44 x i8] c"result = concat(result, any_to_string(args[\00", align 1 +@str.107 = private unnamed_addr constant [6 x i8] c"])); \00", align 1 +@str.108 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.109 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.110 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.111 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.112 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.113 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.114 = private unnamed_addr constant [37 x i8] c"result = concat(result, substr(fmt, \00", align 1 +@str.115 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.116 = private unnamed_addr constant [5 x i8] c")); \00", align 1 +@str.117 = private unnamed_addr constant [6 x i8] c"SxBox\00", align 1 +@str.118 = private unnamed_addr constant [9 x i8] c"setArea:\00", align 1 +@str.119 = private unnamed_addr constant [31 x i8] c"FAIL: width/height round-trip\0A\00", align 1 +@str.120 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.121 = private unnamed_addr constant [31 x i8] c"FAIL: width/height round-trip\0A\00", align 1 +@str.122 = private unnamed_addr constant [31 x i8] c"FAIL: area expected 0, got {}\0A\00", align 1 +@str.123 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.124 = private unnamed_addr constant [31 x i8] c"FAIL: area expected 0, got {}\0A\00", align 1 +@str.125 = private unnamed_addr constant [52 x i8] c"FAIL: setArea: should not be registered (readonly)\0A\00", align 1 +@str.126 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.127 = private unnamed_addr constant [52 x i8] c"FAIL: setArea: should not be registered (readonly)\0A\00", align 1 +@str.128 = private unnamed_addr constant [29 x i8] c"property: w={} h={} area={}\0A\00", align 1 +@str.129 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 +@str.130 = private unnamed_addr constant [29 x i8] c"property: w={} h={} area={}\0A\00", align 1 +@str.131 = private unnamed_addr constant [16 x i8] c"Source_Location\00", align 1 +@str.132 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.133 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.134 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str = private constant [5 x i8] c"file\00" +@fld.str.135 = private constant [5 x i8] c"line\00" +@fld.str.136 = private constant [4 x i8] c"col\00" +@fld.str.137 = private constant [5 x i8] c"func\00" +@field_names = private constant [4 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str, i64 4 }, { ptr, i64 } { ptr @fld.str.135, i64 4 }, { ptr, i64 } { ptr @fld.str.136, i64 3 }, { ptr, i64 } { ptr @fld.str.137, i64 4 }] +@str.138 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.139 = private unnamed_addr constant [10 x i8] c"Allocator\00", align 1 +@str.140 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.141 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.142 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.143 = private constant [4 x i8] c"ctx\00" +@fld.str.144 = private constant [6 x i8] c"alloc\00" +@fld.str.145 = private constant [8 x i8] c"dealloc\00" +@field_names.146 = private constant [3 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.143, i64 3 }, { ptr, i64 } { ptr @fld.str.144, i64 5 }, { ptr, i64 } { ptr @fld.str.145, i64 7 }] +@str.147 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.148 = private unnamed_addr constant [11 x i8] c"CAllocator\00", align 1 +@str.149 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.150 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.151 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@field_names.152 = private constant [0 x { ptr, i64 }] zeroinitializer +@str.153 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.154 = private unnamed_addr constant [4 x i8] c"GPA\00", align 1 +@str.155 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.156 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.157 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.158 = private constant [12 x i8] c"alloc_count\00" +@field_names.159 = private constant [1 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.158, i64 11 }] +@str.160 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.161 = private unnamed_addr constant [11 x i8] c"ArenaChunk\00", align 1 +@str.162 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.163 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.164 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.165 = private constant [5 x i8] c"next\00" +@fld.str.166 = private constant [4 x i8] c"cap\00" +@field_names.167 = private constant [2 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.165, i64 4 }, { ptr, i64 } { ptr @fld.str.166, i64 3 }] +@str.168 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.169 = private unnamed_addr constant [6 x i8] c"Arena\00", align 1 +@str.170 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.171 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.172 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.173 = private constant [6 x i8] c"first\00" +@fld.str.174 = private constant [10 x i8] c"end_index\00" +@fld.str.175 = private constant [7 x i8] c"parent\00" +@field_names.176 = private constant [3 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.173, i64 5 }, { ptr, i64 } { ptr @fld.str.174, i64 9 }, { ptr, i64 } { ptr @fld.str.175, i64 6 }] +@str.177 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.178 = private unnamed_addr constant [9 x i8] c"BufAlloc\00", align 1 +@str.179 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.180 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.181 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.182 = private constant [4 x i8] c"buf\00" +@fld.str.183 = private constant [4 x i8] c"len\00" +@fld.str.184 = private constant [4 x i8] c"pos\00" +@field_names.185 = private constant [3 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.182, i64 3 }, { ptr, i64 } { ptr @fld.str.183, i64 3 }, { ptr, i64 } { ptr @fld.str.184, i64 3 }] +@str.186 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.187 = private unnamed_addr constant [18 x i8] c"TrackingAllocator\00", align 1 +@str.188 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.189 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.190 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.191 = private constant [7 x i8] c"parent\00" +@fld.str.192 = private constant [12 x i8] c"alloc_count\00" +@fld.str.193 = private constant [14 x i8] c"dealloc_count\00" +@fld.str.194 = private constant [18 x i8] c"total_alloc_bytes\00" +@field_names.195 = private constant [4 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.191, i64 6 }, { ptr, i64 } { ptr @fld.str.192, i64 11 }, { ptr, i64 } { ptr @fld.str.193, i64 13 }, { ptr, i64 } { ptr @fld.str.194, i64 17 }] +@str.196 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.197 = private unnamed_addr constant [8 x i8] c"Context\00", align 1 +@str.198 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.199 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.200 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.201 = private constant [10 x i8] c"allocator\00" +@fld.str.202 = private constant [5 x i8] c"data\00" +@field_names.203 = private constant [2 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.201, i64 9 }, { ptr, i64 } { ptr @fld.str.202, i64 4 }] +@str.204 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.205 = private unnamed_addr constant [13 x i8] c"BuildOptions\00", align 1 +@str.206 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.207 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.208 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@field_names.209 = private constant [0 x { ptr, i64 }] zeroinitializer +@str.210 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.211 = private unnamed_addr constant [9 x i8] c"NSString\00", align 1 +@str.212 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.213 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.214 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@field_names.215 = private constant [0 x { ptr, i64 }] zeroinitializer +@str.216 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.217 = private unnamed_addr constant [6 x i8] c"SxBox\00", align 1 +@str.218 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.219 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.220 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@field_names.221 = private constant [0 x { ptr, i64 }] zeroinitializer +@str.222 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.223 = private unnamed_addr constant [13 x i8] c"__SxBoxState\00", align 1 +@str.224 = private unnamed_addr constant [2 x i8] c"{\00", align 1 +@str.225 = private unnamed_addr constant [2 x i8] c"}\00", align 1 +@str.226 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@fld.str.227 = private constant [15 x i8] c"__sx_allocator\00" +@fld.str.228 = private constant [6 x i8] c"width\00" +@fld.str.229 = private constant [7 x i8] c"height\00" +@fld.str.230 = private constant [5 x i8] c"area\00" +@field_names.231 = private constant [4 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.227, i64 14 }, { ptr, i64 } { ptr @fld.str.228, i64 5 }, { ptr, i64 } { ptr @fld.str.229, i64 6 }, { ptr, i64 } { ptr @fld.str.230, i64 4 }] +@str.232 = private unnamed_addr constant [3 x i8] c": \00", align 1 +@str.233 = private unnamed_addr constant [2 x i8] c".\00", align 1 +@fld.str.234 = private constant [6 x i8] c"macos\00" +@fld.str.235 = private constant [6 x i8] c"linux\00" +@fld.str.236 = private constant [8 x i8] c"windows\00" +@fld.str.237 = private constant [5 x i8] c"wasm\00" +@fld.str.238 = private constant [4 x i8] c"ios\00" +@fld.str.239 = private constant [8 x i8] c"android\00" +@fld.str.240 = private constant [8 x i8] c"unknown\00" +@field_names.241 = private constant [7 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.234, i64 5 }, { ptr, i64 } { ptr @fld.str.235, i64 5 }, { ptr, i64 } { ptr @fld.str.236, i64 7 }, { ptr, i64 } { ptr @fld.str.237, i64 4 }, { ptr, i64 } { ptr @fld.str.238, i64 3 }, { ptr, i64 } { ptr @fld.str.239, i64 7 }, { ptr, i64 } { ptr @fld.str.240, i64 7 }] +@str.242 = private unnamed_addr constant [2 x i8] c"(\00", align 1 +@str.243 = private unnamed_addr constant [2 x i8] c")\00", align 1 +@str.244 = private unnamed_addr constant [2 x i8] c".\00", align 1 +@fld.str.245 = private constant [8 x i8] c"aarch64\00" +@fld.str.246 = private constant [7 x i8] c"x86_64\00" +@fld.str.247 = private constant [7 x i8] c"wasm32\00" +@fld.str.248 = private constant [7 x i8] c"wasm64\00" +@fld.str.249 = private constant [8 x i8] c"unknown\00" +@field_names.250 = private constant [5 x { ptr, i64 }] [{ ptr, i64 } { ptr @fld.str.245, i64 7 }, { ptr, i64 } { ptr @fld.str.246, i64 6 }, { ptr, i64 } { ptr @fld.str.247, i64 6 }, { ptr, i64 } { ptr @fld.str.248, i64 6 }, { ptr, i64 } { ptr @fld.str.249, i64 7 }] +@str.251 = private unnamed_addr constant [2 x i8] c"(\00", align 1 +@str.252 = private unnamed_addr constant [2 x i8] c")\00", align 1 +@str.253 = private unnamed_addr constant [2 x i8] c"[\00", align 1 +@str.254 = private unnamed_addr constant [2 x i8] c"]\00", align 1 +@str.255 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.256 = private unnamed_addr constant [2 x i8] c"[\00", align 1 +@str.257 = private unnamed_addr constant [2 x i8] c"]\00", align 1 +@str.258 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.259 = private unnamed_addr constant [2 x i8] c"[\00", align 1 +@str.260 = private unnamed_addr constant [2 x i8] c"]\00", align 1 +@str.261 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.262 = private unnamed_addr constant [2 x i8] c"[\00", align 1 +@str.263 = private unnamed_addr constant [2 x i8] c"]\00", align 1 +@str.264 = private unnamed_addr constant [3 x i8] c", \00", align 1 +@str.265 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.266 = private unnamed_addr constant [6 x i8] c"*void\00", align 1 +@str.267 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.268 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.269 = private unnamed_addr constant [12 x i8] c"*CAllocator\00", align 1 +@str.270 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.271 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.272 = private unnamed_addr constant [5 x i8] c"*GPA\00", align 1 +@str.273 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.274 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.275 = private unnamed_addr constant [12 x i8] c"*ArenaChunk\00", align 1 +@str.276 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.277 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.278 = private unnamed_addr constant [7 x i8] c"*Arena\00", align 1 +@str.279 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.280 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.281 = private unnamed_addr constant [6 x i8] c"[*]u8\00", align 1 +@str.282 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.283 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.284 = private unnamed_addr constant [10 x i8] c"*BufAlloc\00", align 1 +@str.285 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.286 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.287 = private unnamed_addr constant [19 x i8] c"*TrackingAllocator\00", align 1 +@str.288 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.289 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.290 = private unnamed_addr constant [10 x i8] c"*NSString\00", align 1 +@str.291 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.292 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.293 = private unnamed_addr constant [13 x i8] c"**CAllocator\00", align 1 +@str.294 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.295 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.296 = private unnamed_addr constant [5 x i8] c"*s64\00", align 1 +@str.297 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.298 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.299 = private unnamed_addr constant [7 x i8] c"**void\00", align 1 +@str.300 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.301 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.302 = private unnamed_addr constant [7 x i8] c"*SxBox\00", align 1 +@str.303 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.304 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.305 = private unnamed_addr constant [8 x i8] c"**SxBox\00", align 1 +@str.306 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.307 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.308 = private unnamed_addr constant [5 x i8] c"*s32\00", align 1 +@str.309 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.310 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.311 = private unnamed_addr constant [8 x i8] c"*string\00", align 1 +@str.312 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.313 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.314 = private unnamed_addr constant [5 x i8] c"*Any\00", align 1 +@str.315 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.316 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.317 = private unnamed_addr constant [7 x i8] c"*[]Any\00", align 1 +@str.318 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.319 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.320 = private unnamed_addr constant [4 x i8] c"*u8\00", align 1 +@str.321 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.322 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.323 = private unnamed_addr constant [6 x i8] c"*bool\00", align 1 +@str.324 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.325 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.326 = private unnamed_addr constant [8 x i8] c"*[1]Any\00", align 1 +@str.327 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@str.328 = private unnamed_addr constant [5 x i8] c"null\00", align 1 +@str.329 = private unnamed_addr constant [8 x i8] c"*[3]Any\00", align 1 +@str.330 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1 +@OBJC_METH_VAR_NAME_ = private unnamed_addr constant [5 x i8] c"init\00" +@OBJC_METH_VAR_NAME_.331 = private unnamed_addr constant [10 x i8] c"setWidth:\00" +@OBJC_METH_VAR_NAME_.332 = private unnamed_addr constant [11 x i8] c"setHeight:\00" +@OBJC_METH_VAR_NAME_.333 = private unnamed_addr constant [6 x i8] c"width\00" +@OBJC_METH_VAR_NAME_.334 = private unnamed_addr constant [7 x i8] c"height\00" +@OBJC_METH_VAR_NAME_.335 = private unnamed_addr constant [5 x i8] c"area\00" +@OBJC_IVAR_NAME_ = private unnamed_addr constant [11 x i8] c"__sx_state\00" +@OBJC_IVAR_TYPE_ = private unnamed_addr constant [3 x i8] c"^v\00" +@OBJC_CLASS_NAME_ = private unnamed_addr constant [9 x i8] c"NSObject\00" +@OBJC_CLASS_NAME_.336 = private unnamed_addr constant [6 x i8] c"SxBox\00" +@OBJC_METH_VAR_NAME_.337 = private unnamed_addr constant [6 x i8] c"width\00" +@OBJC_METH_VAR_TYPE_ = private unnamed_addr constant [4 x i8] c"i@:\00" +@OBJC_METH_VAR_NAME_.338 = private unnamed_addr constant [10 x i8] c"setWidth:\00" +@OBJC_METH_VAR_TYPE_.339 = private unnamed_addr constant [5 x i8] c"v@:i\00" +@OBJC_METH_VAR_NAME_.340 = private unnamed_addr constant [7 x i8] c"height\00" +@OBJC_METH_VAR_TYPE_.341 = private unnamed_addr constant [4 x i8] c"i@:\00" +@OBJC_METH_VAR_NAME_.342 = private unnamed_addr constant [11 x i8] c"setHeight:\00" +@OBJC_METH_VAR_TYPE_.343 = private unnamed_addr constant [5 x i8] c"v@:i\00" +@OBJC_METH_VAR_NAME_.344 = private unnamed_addr constant [5 x i8] c"area\00" +@OBJC_METH_VAR_TYPE_.345 = private unnamed_addr constant [4 x i8] c"i@:\00" +@OBJC_METH_VAR_NAME_.346 = private unnamed_addr constant [8 x i8] c"dealloc\00" +@OBJC_METH_VAR_TYPE_.347 = private unnamed_addr constant [4 x i8] c"v@:\00" +@OBJC_METH_VAR_NAME_.348 = private unnamed_addr constant [6 x i8] c"alloc\00" +@OBJC_METH_VAR_TYPE_.349 = private unnamed_addr constant [4 x i8] c"@@:\00" +@OBJC_CLASS_NAME_.350 = private unnamed_addr constant [6 x i8] c"SxBox\00" +@llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__sx_objc_selector_init, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__sx_objc_class_init, ptr null }] + +; Function Attrs: nounwind +declare void @out(ptr) #0 + +declare ptr @malloc(i64) + +declare void @free(ptr) + +declare ptr @memcpy(ptr, ptr, i64) + +declare ptr @memset(ptr, i32, i64) + +; Function Attrs: nounwind +define internal ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %2, ptr %allocaN, align 8 + %load = load i64, ptr %allocaN, align 8 + %call = call ptr @malloc(i64 %load) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca ptr, align 8 + store ptr %2, ptr %allocaN, align 8 + %load = load ptr, ptr %allocaN, align 8 + call void @free(ptr %load) + ret void +} + +; Function Attrs: nounwind +declare i64 @GPA.init(ptr) #0 + +; Function Attrs: nounwind +declare ptr @GPA.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @GPA.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.add_chunk(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.init(ptr sret({ ptr, i64, { ptr, ptr, ptr } }), ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.deinit(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @Arena.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.init(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.init(ptr sret({ { ptr, ptr, ptr }, i64, i64, i64 }), ptr, ptr) #0 + +; Function Attrs: nounwind +declare i64 @TrackingAllocator.leak_count(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.report(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @TrackingAllocator.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 { +entry: + %alloca = alloca i64, align 8 + store i64 %1, ptr %alloca, align 8 + %load = load i64, ptr %alloca, align 8 + %add = add i64 %load, 1 + %loadN = load { { ptr, ptr, ptr }, ptr }, ptr %0, align 8 + %sg = extractvalue { { ptr, ptr, ptr }, ptr } %loadN, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 1 + %icall = call ptr %sgN(ptr %0, ptr %sgN, i64 %add) + %allocaN = alloca ptr, align 8 + store ptr %icall, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load i64, ptr %alloca, align 8 + %addN = add i64 %loadN, 1 + %2 = call ptr @memset(ptr %loadN, i32 0, i64 %addN) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } undef, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + %pti = ptrtoint ptr %loadN to i64 + store i64 %pti, ptr %gep, align 8 + %loadN = load i64, ptr %alloca, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 %loadN, ptr %gepN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @int_to_string(ptr %0, i64 %1) #0 { +entry: + %alloca = alloca i64, align 8 + store i64 %1, ptr %alloca, align 8 + %load = load i64, ptr %alloca, align 8 + %icmp = icmp eq i64 %load, 0 + br i1 %icmp, label %if.then.18, label %if.merge.19 + +if.then.18: ; preds = %entry + ret { ptr, i64 } { ptr @str, i64 1 } + +if.merge.19: ; preds = %entry + %loadN = load i64, ptr %alloca, align 8 + %icmpN = icmp slt i64 %loadN, 0 + %allocaN = alloca i1, align 1 + store i1 %icmpN, ptr %allocaN, align 1 + %loadN = load i1, ptr %allocaN, align 1 + br i1 %loadN, label %if.then.20, label %if.else.21 + +if.then.20: ; preds = %if.merge.19 + %loadN = load i64, ptr %alloca, align 8 + %sub = sub i64 0, %loadN + br label %if.merge.22 + +if.else.21: ; preds = %if.merge.19 + %loadN = load i64, ptr %alloca, align 8 + br label %if.merge.22 + +if.merge.22: ; preds = %if.else.21, %if.then.20 + %bp = phi i64 [ %sub, %if.then.20 ], [ %loadN, %if.else.21 ] + %allocaN = alloca i64, align 8 + store i64 %bp, ptr %allocaN, align 8 + %call = call { ptr, i64 } @cstring(ptr %0, i64 20) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 19, ptr %allocaN, align 8 + br label %while.hdr.23 + +while.hdr.23: ; preds = %while.body.24, %if.merge.22 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %while.body.24, label %while.exit.25 + +while.body.24: ; preds = %while.hdr.23 + %loadN = load i64, ptr %allocaN, align 8 + %srem = srem i64 %loadN, 10 + %add = add i64 %srem, 48 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN + %trunc = trunc i64 %add to i8 + store i8 %trunc, ptr %igp.ptr, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %sdiv = sdiv i64 %loadN, 10 + store i64 %sdiv, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, 1 + store i64 %subN, ptr %allocaN, align 8 + br label %while.hdr.23 + +while.exit.25: ; preds = %while.hdr.23 + %loadN = load i1, ptr %allocaN, align 1 + br i1 %loadN, label %if.then.26, label %if.merge.27 + +if.then.26: ; preds = %while.exit.25 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data21 = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr22 = getelementptr i8, ptr %igp.data21, i64 %loadN + store i8 45, ptr %igp.ptr22, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, 1 + store i64 %subN, ptr %allocaN, align 8 + br label %if.merge.27 + +if.merge.27: ; preds = %if.then.26, %while.exit.25 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 20, %loadN + %subN = sub i64 %subN, 1 + %callN = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 %addN, i64 %subN) + ret { ptr, i64 } %callN +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @bool_to_string(ptr %0, i1 %1) #0 { +entry: + %alloca = alloca i1, align 1 + store i1 %1, ptr %alloca, align 1 + %load = load i1, ptr %alloca, align 1 + br i1 %load, label %if.then.62, label %if.else.63 + +if.then.62: ; preds = %entry + br label %if.merge.64 + +if.else.63: ; preds = %entry + br label %if.merge.64 + +if.merge.64: ; preds = %if.else.63, %if.then.62 + %bp = phi { ptr, i64 } [ { ptr @str.1, i64 4 }, %if.then.62 ], [ { ptr @str.2, i64 5 }, %if.else.63 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @float_to_string(ptr %0, double %1) #0 { +entry: + %alloca = alloca double, align 8 + store double %1, ptr %alloca, align 8 + %load = load double, ptr %alloca, align 8 + %fcmp = fcmp olt double %load, 0.000000e+00 + %allocaN = alloca i1, align 1 + store i1 %fcmp, ptr %allocaN, align 1 + %loadN = load i1, ptr %allocaN, align 1 + br i1 %loadN, label %if.then.68, label %if.else.69 + +if.then.68: ; preds = %entry + %loadN = load double, ptr %alloca, align 8 + %fsub = fsub double 0.000000e+00, %loadN + br label %if.merge.70 + +if.else.69: ; preds = %entry + %loadN = load double, ptr %alloca, align 8 + br label %if.merge.70 + +if.merge.70: ; preds = %if.else.69, %if.then.68 + %bp = phi double [ %fsub, %if.then.68 ], [ %loadN, %if.else.69 ] + %allocaN = alloca double, align 8 + store double %bp, ptr %allocaN, align 8 + %loadN = load double, ptr %allocaN, align 8 + %fptosi = fptosi double %loadN to i64 + %allocaN = alloca i64, align 8 + store i64 %fptosi, ptr %allocaN, align 8 + %loadN = load double, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %sitofp = sitofp i64 %loadN to double + %fsubN = fsub double %loadN, %sitofp + %fmul = fmul double %fsubN, 1.000000e+06 + %fptosiN = fptosi double %fmul to i64 + %allocaN = alloca i64, align 8 + store i64 %fptosiN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %loadN, 0 + br i1 %icmp, label %if.then.71, label %if.merge.72 + +if.then.71: ; preds = %if.merge.70 + %loadN = load i64, ptr %allocaN, align 8 + %sub = sub i64 0, %loadN + store i64 %sub, ptr %allocaN, align 8 + br label %if.merge.72 + +if.merge.72: ; preds = %if.then.71, %if.merge.70 + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %len = extractvalue { ptr, i64 } %loadN, 1 + %allocaN = alloca i64, align 8 + store i64 %len, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %allocaN = alloca i64, align 8 + store i64 %lenN, ptr %allocaN, align 8 + %loadN = load i1, ptr %allocaN, align 1 + br i1 %loadN, label %if.then.73, label %if.else.74 + +if.then.73: ; preds = %if.merge.72 + br label %if.merge.75 + +if.else.74: ; preds = %if.merge.72 + br label %if.merge.75 + +if.merge.75: ; preds = %if.else.74, %if.then.73 + %bpN = phi i64 [ 1, %if.then.73 ], [ 0, %if.else.74 ] + %allocaN = alloca i64, align 8 + store i64 %bpN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, %loadN + %addN = add i64 %add, 1 + %addN = add i64 %addN, 6 + %allocaN = alloca i64, align 8 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @cstring(ptr %0, i64 %loadN) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + %loadN = load i1, ptr %allocaN, align 1 + br i1 %loadN, label %if.then.76, label %if.merge.77 + +if.then.76: ; preds = %if.merge.75 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 0 + store i8 45, ptr %igp.ptr, align 1 + store i64 1, ptr %allocaN, align 8 + br label %if.merge.77 + +if.merge.77: ; preds = %if.then.76, %if.merge.75 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data41 = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr42 = getelementptr i8, ptr %igp.data41, i64 %loadN + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptr = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %igp.ptr42, ptr %dptr, i64 %loadN) + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, %loadN + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data51 = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr52 = getelementptr i8, ptr %igp.data51, i64 %loadN + store i8 46, ptr %igp.ptr52, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 6, %loadN + %allocaN = alloca i64, align 8 + store i64 %subN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data60 = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr61 = getelementptr i8, ptr %igp.data60, i64 %loadN + %loadN = load i64, ptr %allocaN, align 8 + %2 = call ptr @memset(ptr %igp.ptr61, i32 48, i64 %loadN) + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, %loadN + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data68 = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr69 = getelementptr i8, ptr %igp.data68, i64 %loadN + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptrN = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %igp.ptr69, ptr %dptrN, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +define internal void @hex_group(ptr %0, { ptr, i64 } %1, i64 %2, i64 %3) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %2, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 %3, ptr %allocaN, align 8 + %load = load i64, ptr %allocaN, align 8 + %add = add i64 %load, 3 + %allocaN = alloca i64, align 8 + store i64 %add, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 %loadN, ptr %allocaN, align 8 + br label %while.hdr.231 + +while.hdr.231: ; preds = %if.merge.236, %entry + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp sge i64 %loadN, %loadN + br i1 %icmp, label %while.body.232, label %while.exit.233 + +while.body.232: ; preds = %while.hdr.231 + %loadN = load i64, ptr %allocaN, align 8 + %srem = srem i64 %loadN, 16 + %allocaN = alloca i64, align 8 + store i64 %srem, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp slt i64 %loadN, 10 + br i1 %icmpN, label %if.then.234, label %if.else.235 + +while.exit.233: ; preds = %while.hdr.231 + ret void + +if.then.234: ; preds = %while.body.232 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 48 + br label %if.merge.236 + +if.else.235: ; preds = %while.body.232 + %loadN = load i64, ptr %allocaN, align 8 + %sub = sub i64 %loadN, 10 + %addN = add i64 %sub, 97 + br label %if.merge.236 + +if.merge.236: ; preds = %if.else.235, %if.then.234 + %bp = phi i64 [ %addN, %if.then.234 ], [ %addN, %if.else.235 ] + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN + %trunc = trunc i64 %bp to i8 + store i8 %trunc, ptr %igp.ptr, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %sdiv = sdiv i64 %loadN, 16 + store i64 %sdiv, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, 1 + store i64 %subN, ptr %allocaN, align 8 + br label %while.hdr.231 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @int_to_hex_string(ptr %0, i64 %1) #0 { +entry: + %alloca = alloca i64, align 8 + store i64 %1, ptr %alloca, align 8 + %load = load i64, ptr %alloca, align 8 + %icmp = icmp eq i64 %load, 0 + br i1 %icmp, label %if.then.221, label %if.merge.222 + +if.then.221: ; preds = %entry + ret { ptr, i64 } { ptr @str.3, i64 1 } + +if.merge.222: ; preds = %entry + %loadN = load i64, ptr %alloca, align 8 + %srem = srem i64 %loadN, 65536 + %allocaN = alloca i64, align 8 + store i64 %srem, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp slt i64 %loadN, 0 + br i1 %icmpN, label %if.then.223, label %if.merge.224 + +if.then.223: ; preds = %if.merge.222 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 65536 + store i64 %add, ptr %allocaN, align 8 + br label %if.merge.224 + +if.merge.224: ; preds = %if.then.223, %if.merge.222 + %loadN = load i64, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %sub = sub i64 %loadN, %loadN + %sdiv = sdiv i64 %sub, 65536 + %allocaN = alloca i64, align 8 + store i64 %sdiv, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %sremN = srem i64 %loadN, 65536 + %allocaN = alloca i64, align 8 + store i64 %sremN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp slt i64 %loadN, 0 + br i1 %icmpN, label %if.then.225, label %if.merge.226 + +if.then.225: ; preds = %if.merge.224 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 65536 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.226 + +if.merge.226: ; preds = %if.then.225, %if.merge.224 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, %loadN + %sdivN = sdiv i64 %subN, 65536 + %allocaN = alloca i64, align 8 + store i64 %sdivN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %sremN = srem i64 %loadN, 65536 + %allocaN = alloca i64, align 8 + store i64 %sremN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp slt i64 %loadN, 0 + br i1 %icmpN, label %if.then.227, label %if.merge.228 + +if.then.227: ; preds = %if.merge.226 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 65536 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.228 + +if.merge.228: ; preds = %if.then.227, %if.merge.226 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, %loadN + %sdivN = sdiv i64 %subN, 65536 + %allocaN = alloca i64, align 8 + store i64 %sdivN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %sremN = srem i64 %loadN, 65536 + %allocaN = alloca i64, align 8 + store i64 %sremN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp slt i64 %loadN, 0 + br i1 %icmpN, label %if.then.229, label %if.merge.230 + +if.then.229: ; preds = %if.merge.228 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 65536 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.230 + +if.merge.230: ; preds = %if.then.229, %if.merge.228 + %call = call { ptr, i64 } @cstring(ptr %0, i64 16) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + call void @hex_group(ptr %0, { ptr, i64 } %loadN, i64 0, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + call void @hex_group(ptr %0, { ptr, i64 } %loadN, i64 4, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + call void @hex_group(ptr %0, { ptr, i64 } %loadN, i64 8, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + call void @hex_group(ptr %0, { ptr, i64 } %loadN, i64 12, i64 %loadN) + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.237 + +while.hdr.237: ; preds = %if.merge.241, %if.merge.230 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp slt i64 %loadN, 15 + br i1 %icmpN, label %while.body.238, label %while.exit.239 + +while.body.238: ; preds = %while.hdr.237 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.data = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr = getelementptr i8, ptr %ig.data, i64 %loadN + %ig.val = load i8, ptr %ig.ptr, align 1 + %cmp.ext = zext i8 %ig.val to i64 + %icmpN = icmp ne i64 %cmp.ext, 48 + br i1 %icmpN, label %if.then.240, label %if.merge.241 + +while.exit.239: ; preds = %if.then.240, %while.hdr.237 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 16, %loadN + %callN = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 %loadN, i64 %subN) + ret { ptr, i64 } %callN + +if.then.240: ; preds = %while.body.238 + br label %while.exit.239 + +if.merge.241: ; preds = %while.body.238 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %while.hdr.237 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @concat(ptr %0, { ptr, i64 } %1, { ptr, i64 } %2) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %2, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %alloca, align 8 + %len = extractvalue { ptr, i64 } %load, 1 + %allocaN = alloca i64, align 8 + store i64 %len, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %allocaN = alloca i64, align 8 + store i64 %lenN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, %loadN + %call = call { ptr, i64 } @cstring(ptr %0, i64 %add) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptr = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %dptrN = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %dptr, ptr %dptrN, i64 %loadN) + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptrN = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %igp.ptr, ptr %dptrN, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @substr(ptr %0, { ptr, i64 } %1, i64 %2, i64 %3) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %2, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 %3, ptr %allocaN, align 8 + %load = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @cstring(ptr %0, i64 %load) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %dptr = extractvalue { ptr, i64 } %loadN, 0 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %igp.data = extractvalue { ptr, i64 } %loadN, 0 + %igp.ptr = getelementptr i8, ptr %igp.data, i64 %loadN + %loadN = load i64, ptr %allocaN, align 8 + %callN = call ptr @memcpy(ptr %dptr, ptr %igp.ptr, i64 %loadN) + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +declare ptr @xml_escape(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @path_join(ptr, ptr) #0 + +; Function Attrs: nounwind +define internal { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %1) #0 { +entry: + %alloca = alloca { i64, i64 }, align 8 + store { i64, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.4, i64 3 }, ptr %allocaN, align 8 + %load = load { i64, i64 }, ptr %alloca, align 8 + %sg = extractvalue { i64, i64 } %load, 0 + %ba.val = insertvalue { i64, i64 } { i64 13, i64 undef }, i64 %sg, 1 + %allocaN = alloca { i64, i64 }, align 8 + store { i64, i64 } %ba.val, ptr %allocaN, align 8 + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %ua.raw = extractvalue { i64, i64 } %loadN, 1 + switch i64 %ua.raw, label %match.unr.61 [ + i64 17, label %match.arm.47 + i64 2, label %match.arm.48 + i64 3, label %match.arm.48 + i64 4, label %match.arm.48 + i64 5, label %match.arm.48 + i64 6, label %match.arm.48 + i64 7, label %match.arm.48 + i64 8, label %match.arm.48 + i64 9, label %match.arm.48 + i64 16, label %match.arm.48 + i64 15, label %match.arm.48 + i64 12, label %match.arm.49 + i64 1, label %match.arm.50 + i64 10, label %match.arm.51 + i64 11, label %match.arm.51 + i64 19, label %match.arm.52 + i64 20, label %match.arm.52 + i64 21, label %match.arm.52 + i64 23, label %match.arm.52 + i64 25, label %match.arm.52 + i64 27, label %match.arm.52 + i64 30, label %match.arm.52 + i64 32, label %match.arm.52 + i64 34, label %match.arm.52 + i64 38, label %match.arm.52 + i64 40, label %match.arm.52 + i64 46, label %match.arm.52 + i64 48, label %match.arm.52 + i64 36, label %match.arm.53 + i64 37, label %match.arm.53 + i64 57, label %match.arm.56 + i64 59, label %match.arm.56 + i64 35, label %match.arm.57 + i64 52, label %match.arm.57 + i64 18, label %match.arm.58 + i64 22, label %match.arm.58 + i64 24, label %match.arm.58 + i64 26, label %match.arm.58 + i64 28, label %match.arm.58 + i64 29, label %match.arm.58 + i64 31, label %match.arm.58 + i64 33, label %match.arm.58 + i64 41, label %match.arm.58 + i64 43, label %match.arm.58 + i64 44, label %match.arm.58 + i64 45, label %match.arm.58 + i64 47, label %match.arm.58 + i64 49, label %match.arm.58 + i64 50, label %match.arm.58 + i64 51, label %match.arm.58 + i64 53, label %match.arm.58 + i64 54, label %match.arm.58 + i64 55, label %match.arm.58 + i64 56, label %match.arm.58 + i64 58, label %match.arm.58 + i64 60, label %match.arm.58 + i64 13, label %match.arm.60 + ] + +match.merge.46: ; preds = %dispatch.merge.194, %dispatch.merge.180, %dispatch.merge.166, %dispatch.merge.158, %dispatch.merge.78, %float.merge.67, %match.unr.61, %match.arm.60, %match.arm.50, %match.arm.49, %match.arm.48, %match.arm.47 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN + +match.arm.47: ; preds = %entry + store { ptr, i64 } { ptr @str.5, i64 0 }, ptr %allocaN, align 8 + br label %match.merge.46 + +match.arm.48: ; preds = %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %ua.raw6 = extractvalue { i64, i64 } %loadN, 1 + %call = call { ptr, i64 } @int_to_string(ptr %0, i64 %ua.raw6) + store { ptr, i64 } %call, ptr %allocaN, align 8 + br label %match.merge.46 + +match.arm.49: ; preds = %entry + %allocaN = alloca { ptr, i64 }, align 8 + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %ua.raw9 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr = inttoptr i64 %ua.raw9 to ptr + %ua.load = load { ptr, i64 }, ptr %ua.ptr, align 8 + store { ptr, i64 } %ua.load, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + store { ptr, i64 } %loadN, ptr %allocaN, align 8 + br label %match.merge.46 + +match.arm.50: ; preds = %entry + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %ua.raw12 = extractvalue { i64, i64 } %loadN, 1 + %tr = trunc i64 %ua.raw12 to i1 + %callN = call { ptr, i64 } @bool_to_string(ptr %0, i1 %tr) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %match.merge.46 + +match.arm.51: ; preds = %entry, %entry + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %allocaN = alloca double, align 8 + %sgN = extractvalue { i64, i64 } %loadN, 0 + %icmp = icmp eq i64 %sgN, 10 + br i1 %icmp, label %fN.unbox.65, label %fN.unbox.66 + +match.arm.52: ; preds = %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %ua.raw18 = extractvalue { i64, i64 } %loadN, 1 + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + switch i64 %ua.raw18, label %dispatch.default.79 [ + i64 19, label %dispatch.case.80 + i64 20, label %dispatch.case.81 + i64 21, label %dispatch.case.82 + i64 23, label %dispatch.case.83 + i64 25, label %dispatch.case.84 + i64 27, label %dispatch.case.85 + i64 30, label %dispatch.case.86 + i64 32, label %dispatch.case.87 + i64 34, label %dispatch.case.88 + i64 38, label %dispatch.case.89 + i64 40, label %dispatch.case.90 + i64 46, label %dispatch.case.91 + i64 48, label %dispatch.case.92 + ] + +match.arm.53: ; preds = %entry, %entry + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %ua.raw22 = extractvalue { i64, i64 } %loadN, 1 + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + switch i64 %ua.raw22, label %dispatch.default.159 [ + i64 36, label %dispatch.case.160 + i64 37, label %dispatch.case.161 + ] + +match.arm.54: ; No predecessors! + unreachable + +match.arm.55: ; No predecessors! + unreachable + +match.arm.56: ; preds = %entry, %entry + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %ua.raw26 = extractvalue { i64, i64 } %loadN, 1 + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + switch i64 %ua.raw26, label %dispatch.default.167 [ + i64 57, label %dispatch.case.168 + i64 59, label %dispatch.case.169 + ] + +match.arm.57: ; preds = %entry, %entry + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %ua.raw30 = extractvalue { i64, i64 } %loadN, 1 + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + switch i64 %ua.raw30, label %dispatch.default.181 [ + i64 35, label %dispatch.case.182 + i64 52, label %dispatch.case.183 + ] + +match.arm.58: ; preds = %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry, %entry + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %ua.raw34 = extractvalue { i64, i64 } %loadN, 1 + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + switch i64 %ua.raw34, label %dispatch.default.195 [ + i64 18, label %dispatch.case.196 + i64 22, label %dispatch.case.197 + i64 24, label %dispatch.case.198 + i64 26, label %dispatch.case.199 + i64 28, label %dispatch.case.200 + i64 29, label %dispatch.case.201 + i64 31, label %dispatch.case.202 + i64 33, label %dispatch.case.203 + i64 41, label %dispatch.case.204 + i64 43, label %dispatch.case.205 + i64 44, label %dispatch.case.206 + i64 45, label %dispatch.case.207 + i64 47, label %dispatch.case.208 + i64 49, label %dispatch.case.209 + i64 50, label %dispatch.case.210 + i64 51, label %dispatch.case.211 + i64 53, label %dispatch.case.212 + i64 54, label %dispatch.case.213 + i64 55, label %dispatch.case.214 + i64 56, label %dispatch.case.215 + i64 58, label %dispatch.case.216 + i64 60, label %dispatch.case.217 + ] + +match.arm.59: ; No predecessors! + unreachable + +match.arm.60: ; preds = %entry + %loadN = load { i64, i64 }, ptr %alloca, align 8 + %tn.tid = extractvalue { i64, i64 } %loadN, 1 + %tn.gep = getelementptr inbounds [92 x { ptr, i64 }], ptr @__sx_type_names, i64 0, i64 %tn.tid + %tn.load = load { ptr, i64 }, ptr %tn.gep, align 8 + store { ptr, i64 } %tn.load, ptr %allocaN, align 8 + br label %match.merge.46 + +match.unr.61: ; preds = %entry + br label %match.merge.46 + +f32.unbox.65: ; preds = %match.arm.51 + %ua.raw38 = extractvalue { i64, i64 } %loadN, 1 + %trN = trunc i64 %ua.raw38 to i32 + %iNf = bitcast i32 %trN to float + %fpext = fpext float %iNf to double + store double %fpext, ptr %allocaN, align 8 + br label %float.merge.67 + +f64.unbox.66: ; preds = %match.arm.51 + %ua.raw39 = extractvalue { i64, i64 } %loadN, 1 + %iNd = bitcast i64 %ua.raw39 to double + store double %iNd, ptr %allocaN, align 8 + br label %float.merge.67 + +float.merge.67: ; preds = %fN.unbox.66, %fN.unbox.65 + %loadN = load double, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @float_to_string(ptr %0, double %loadN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %match.merge.46 + +dispatch.merge.78: ; preds = %dispatch.case.92, %dispatch.case.91, %dispatch.case.90, %dispatch.case.89, %dispatch.case.88, %dispatch.case.87, %dispatch.case.86, %dispatch.case.85, %dispatch.case.84, %dispatch.case.83, %dispatch.case.82, %dispatch.case.81, %dispatch.case.80, %dispatch.default.79 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + store { ptr, i64 } %loadN, ptr %allocaN, align 8 + br label %match.merge.46 + +dispatch.default.79: ; preds = %match.arm.52 + store { ptr, i64 } { ptr @str.97, i64 0 }, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.80: ; preds = %match.arm.52 + %ua.raw43 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr44 = inttoptr i64 %ua.raw43 to ptr + %ua.load45 = load { { ptr, i64 }, i32, i32, { ptr, i64 } }, ptr %ua.ptr44, align 8 + %callN = call { ptr, i64 } @struct_to_string__Source_Location(ptr %0, { { ptr, i64 }, i32, i32, { ptr, i64 } } %ua.load45) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.81: ; preds = %match.arm.52 + %ua.raw47 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr48 = inttoptr i64 %ua.raw47 to ptr + %ua.load49 = load { ptr, ptr, ptr }, ptr %ua.ptr48, align 8 + %callN = call { ptr, i64 } @struct_to_string__Allocator(ptr %0, { ptr, ptr, ptr } %ua.load49) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.82: ; preds = %match.arm.52 + %ua.raw51 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr52 = inttoptr i64 %ua.raw51 to ptr + %ua.load53 = load {}, ptr %ua.ptr52, align 1 + %callN = call { ptr, i64 } @struct_to_string__CAllocator(ptr %0, {} %ua.load53) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.83: ; preds = %match.arm.52 + %ua.raw55 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr56 = inttoptr i64 %ua.raw55 to ptr + %ua.load57 = load { i64 }, ptr %ua.ptr56, align 8 + %callN = call { ptr, i64 } @struct_to_string__GPA(ptr %0, { i64 } %ua.load57) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.84: ; preds = %match.arm.52 + %ua.raw59 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr60 = inttoptr i64 %ua.raw59 to ptr + %ua.load61 = load { ptr, i64 }, ptr %ua.ptr60, align 8 + %callN = call { ptr, i64 } @struct_to_string__ArenaChunk(ptr %0, { ptr, i64 } %ua.load61) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.85: ; preds = %match.arm.52 + %ua.raw63 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr64 = inttoptr i64 %ua.raw63 to ptr + %ua.load65 = load { ptr, i64, { ptr, ptr, ptr } }, ptr %ua.ptr64, align 8 + %callN = call { ptr, i64 } @struct_to_string__Arena(ptr %0, { ptr, i64, { ptr, ptr, ptr } } %ua.load65) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.86: ; preds = %match.arm.52 + %ua.raw67 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr68 = inttoptr i64 %ua.raw67 to ptr + %ua.load69 = load { ptr, i64, i64 }, ptr %ua.ptr68, align 8 + %callN = call { ptr, i64 } @struct_to_string__BufAlloc(ptr %0, { ptr, i64, i64 } %ua.load69) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.87: ; preds = %match.arm.52 + %ua.raw71 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr72 = inttoptr i64 %ua.raw71 to ptr + %ua.load73 = load { { ptr, ptr, ptr }, i64, i64, i64 }, ptr %ua.ptr72, align 8 + %callN = call { ptr, i64 } @struct_to_string__TrackingAllocator(ptr %0, { { ptr, ptr, ptr }, i64, i64, i64 } %ua.load73) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.88: ; preds = %match.arm.52 + %ua.raw75 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr76 = inttoptr i64 %ua.raw75 to ptr + %ua.load77 = load { { ptr, ptr, ptr }, ptr }, ptr %ua.ptr76, align 8 + %callN = call { ptr, i64 } @struct_to_string__Context(ptr %0, { { ptr, ptr, ptr }, ptr } %ua.load77) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.89: ; preds = %match.arm.52 + %ua.raw79 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr80 = inttoptr i64 %ua.raw79 to ptr + %ua.load81 = load {}, ptr %ua.ptr80, align 1 + %callN = call { ptr, i64 } @struct_to_string__BuildOptions(ptr %0, {} %ua.load81) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.90: ; preds = %match.arm.52 + %ua.raw83 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr84 = inttoptr i64 %ua.raw83 to ptr + %ua.load85 = load {}, ptr %ua.ptr84, align 1 + %callN = call { ptr, i64 } @struct_to_string__NSString(ptr %0, {} %ua.load85) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.91: ; preds = %match.arm.52 + %ua.raw87 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr88 = inttoptr i64 %ua.raw87 to ptr + %ua.load89 = load {}, ptr %ua.ptr88, align 1 + %callN = call { ptr, i64 } @struct_to_string__SxBox(ptr %0, {} %ua.load89) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.case.92: ; preds = %match.arm.52 + %ua.raw91 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr92 = inttoptr i64 %ua.raw91 to ptr + %ua.load93 = load { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %ua.ptr92, align 8 + %callN = call { ptr, i64 } @struct_to_string____SxBoxState(ptr %0, { { ptr, ptr, ptr }, i32, i32, i32 } %ua.load93) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.78 + +dispatch.merge.158: ; preds = %dispatch.case.161, %dispatch.case.160, %dispatch.default.159 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + store { ptr, i64 } %loadN, ptr %allocaN, align 8 + br label %match.merge.46 + +dispatch.default.159: ; preds = %match.arm.53 + store { ptr, i64 } { ptr @str.98, i64 0 }, ptr %allocaN, align 8 + br label %dispatch.merge.158 + +dispatch.case.160: ; preds = %match.arm.53 + %ua.raw96 = extractvalue { i64, i64 } %loadN, 1 + %callN = call { ptr, i64 } @enum_to_string__OperatingSystem(ptr %0, i64 %ua.raw96) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.158 + +dispatch.case.161: ; preds = %match.arm.53 + %ua.raw98 = extractvalue { i64, i64 } %loadN, 1 + %callN = call { ptr, i64 } @enum_to_string__Architecture(ptr %0, i64 %ua.raw98) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.158 + +dispatch.merge.166: ; preds = %dispatch.case.169, %dispatch.case.168, %dispatch.default.167 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + store { ptr, i64 } %loadN, ptr %allocaN, align 8 + br label %match.merge.46 + +dispatch.default.167: ; preds = %match.arm.56 + store { ptr, i64 } { ptr @str.99, i64 0 }, ptr %allocaN, align 8 + br label %dispatch.merge.166 + +dispatch.case.168: ; preds = %match.arm.56 + %ua.raw101 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr102 = inttoptr i64 %ua.raw101 to ptr + %ua.load103 = load [1 x { i64, i64 }], ptr %ua.ptr102, align 8 + %callN = call { ptr, i64 } @array_to_string__AR_1_Any(ptr %0, [1 x { i64, i64 }] %ua.load103) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.166 + +dispatch.case.169: ; preds = %match.arm.56 + %ua.raw105 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr106 = inttoptr i64 %ua.raw105 to ptr + %ua.load107 = load [3 x { i64, i64 }], ptr %ua.ptr106, align 8 + %callN = call { ptr, i64 } @array_to_string__AR_3_Any(ptr %0, [3 x { i64, i64 }] %ua.load107) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.166 + +dispatch.merge.180: ; preds = %dispatch.case.183, %dispatch.case.182, %dispatch.default.181 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + store { ptr, i64 } %loadN, ptr %allocaN, align 8 + br label %match.merge.46 + +dispatch.default.181: ; preds = %match.arm.57 + store { ptr, i64 } { ptr @str.100, i64 0 }, ptr %allocaN, align 8 + br label %dispatch.merge.180 + +dispatch.case.182: ; preds = %match.arm.57 + %ua.raw110 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr111 = inttoptr i64 %ua.raw110 to ptr + %ua.load112 = load { ptr, i64 }, ptr %ua.ptr111, align 8 + %callN = call { ptr, i64 } @slice_to_string__string(ptr %0, { ptr, i64 } %ua.load112) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.180 + +dispatch.case.183: ; preds = %match.arm.57 + %ua.raw114 = extractvalue { i64, i64 } %loadN, 1 + %ua.ptr115 = inttoptr i64 %ua.raw114 to ptr + %ua.load116 = load { ptr, i64 }, ptr %ua.ptr115, align 8 + %callN = call { ptr, i64 } @slice_to_string__Any(ptr %0, { ptr, i64 } %ua.load116) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.180 + +dispatch.merge.194: ; preds = %dispatch.case.217, %dispatch.case.216, %dispatch.case.215, %dispatch.case.214, %dispatch.case.213, %dispatch.case.212, %dispatch.case.211, %dispatch.case.210, %dispatch.case.209, %dispatch.case.208, %dispatch.case.207, %dispatch.case.206, %dispatch.case.205, %dispatch.case.204, %dispatch.case.203, %dispatch.case.202, %dispatch.case.201, %dispatch.case.200, %dispatch.case.199, %dispatch.case.198, %dispatch.case.197, %dispatch.case.196, %dispatch.default.195 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + store { ptr, i64 } %loadN, ptr %allocaN, align 8 + br label %match.merge.46 + +dispatch.default.195: ; preds = %match.arm.58 + store { ptr, i64 } { ptr @str.101, i64 0 }, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.196: ; preds = %match.arm.58 + %ua.raw119 = extractvalue { i64, i64 } %loadN, 1 + %iNp = inttoptr i64 %ua.raw119 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_void(ptr %0, ptr %iNp) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.197: ; preds = %match.arm.58 + %ua.raw121 = extractvalue { i64, i64 } %loadN, 1 + %iNp122 = inttoptr i64 %ua.raw121 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_CAllocator(ptr %0, ptr %iNp122) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.198: ; preds = %match.arm.58 + %ua.raw124 = extractvalue { i64, i64 } %loadN, 1 + %iNp125 = inttoptr i64 %ua.raw124 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_GPA(ptr %0, ptr %iNp125) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.199: ; preds = %match.arm.58 + %ua.raw127 = extractvalue { i64, i64 } %loadN, 1 + %iNp128 = inttoptr i64 %ua.raw127 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_ArenaChunk(ptr %0, ptr %iNp128) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.200: ; preds = %match.arm.58 + %ua.raw130 = extractvalue { i64, i64 } %loadN, 1 + %iNp131 = inttoptr i64 %ua.raw130 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_Arena(ptr %0, ptr %iNp131) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.201: ; preds = %match.arm.58 + %ua.raw133 = extractvalue { i64, i64 } %loadN, 1 + %iNp134 = inttoptr i64 %ua.raw133 to ptr + %callN = call { ptr, i64 } @pointer_to_string__mptr_u8(ptr %0, ptr %iNp134) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.202: ; preds = %match.arm.58 + %ua.raw136 = extractvalue { i64, i64 } %loadN, 1 + %iNp137 = inttoptr i64 %ua.raw136 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_BufAlloc(ptr %0, ptr %iNp137) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.203: ; preds = %match.arm.58 + %ua.raw139 = extractvalue { i64, i64 } %loadN, 1 + %iNp140 = inttoptr i64 %ua.raw139 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_TrackingAllocator(ptr %0, ptr %iNp140) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.204: ; preds = %match.arm.58 + %ua.raw142 = extractvalue { i64, i64 } %loadN, 1 + %iNp143 = inttoptr i64 %ua.raw142 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_NSString(ptr %0, ptr %iNp143) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.205: ; preds = %match.arm.58 + %ua.raw145 = extractvalue { i64, i64 } %loadN, 1 + %iNp146 = inttoptr i64 %ua.raw145 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_ptr_CAllocator(ptr %0, ptr %iNp146) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.206: ; preds = %match.arm.58 + %ua.raw148 = extractvalue { i64, i64 } %loadN, 1 + %iNp149 = inttoptr i64 %ua.raw148 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_s64(ptr %0, ptr %iNp149) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.207: ; preds = %match.arm.58 + %ua.raw151 = extractvalue { i64, i64 } %loadN, 1 + %iNp152 = inttoptr i64 %ua.raw151 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_ptr_void(ptr %0, ptr %iNp152) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.208: ; preds = %match.arm.58 + %ua.raw154 = extractvalue { i64, i64 } %loadN, 1 + %iNp155 = inttoptr i64 %ua.raw154 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_SxBox(ptr %0, ptr %iNp155) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.209: ; preds = %match.arm.58 + %ua.raw157 = extractvalue { i64, i64 } %loadN, 1 + %iNp158 = inttoptr i64 %ua.raw157 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_ptr_SxBox(ptr %0, ptr %iNp158) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.210: ; preds = %match.arm.58 + %ua.raw160 = extractvalue { i64, i64 } %loadN, 1 + %iNp161 = inttoptr i64 %ua.raw160 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_s32(ptr %0, ptr %iNp161) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.211: ; preds = %match.arm.58 + %ua.raw163 = extractvalue { i64, i64 } %loadN, 1 + %iNp164 = inttoptr i64 %ua.raw163 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_string(ptr %0, ptr %iNp164) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.212: ; preds = %match.arm.58 + %ua.raw166 = extractvalue { i64, i64 } %loadN, 1 + %iNp167 = inttoptr i64 %ua.raw166 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_Any(ptr %0, ptr %iNp167) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.213: ; preds = %match.arm.58 + %ua.raw169 = extractvalue { i64, i64 } %loadN, 1 + %iNp170 = inttoptr i64 %ua.raw169 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_SL_Any(ptr %0, ptr %iNp170) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.214: ; preds = %match.arm.58 + %ua.raw172 = extractvalue { i64, i64 } %loadN, 1 + %iNp173 = inttoptr i64 %ua.raw172 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_u8(ptr %0, ptr %iNp173) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.215: ; preds = %match.arm.58 + %ua.raw175 = extractvalue { i64, i64 } %loadN, 1 + %iNp176 = inttoptr i64 %ua.raw175 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_bool(ptr %0, ptr %iNp176) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.216: ; preds = %match.arm.58 + %ua.raw178 = extractvalue { i64, i64 } %loadN, 1 + %iNp179 = inttoptr i64 %ua.raw178 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_AR_1_Any(ptr %0, ptr %iNp179) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 + +dispatch.case.217: ; preds = %match.arm.58 + %ua.raw181 = extractvalue { i64, i64 } %loadN, 1 + %iNp182 = inttoptr i64 %ua.raw181 to ptr + %callN = call { ptr, i64 } @pointer_to_string__ptr_AR_3_Any(ptr %0, ptr %iNp182) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %dispatch.merge.194 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @build_format(ptr %0, { ptr, i64 } %1) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.102, i64 14 }, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.4 + +while.hdr.4: ; preds = %if.merge.9, %entry + %load = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %len = extractvalue { ptr, i64 } %loadN, 1 + %icmp = icmp slt i64 %load, %len + br i1 %icmp, label %while.body.5, label %while.exit.6 + +while.body.5: ; preds = %while.hdr.4 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.data = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr = getelementptr i8, ptr %ig.data, i64 %loadN + %ig.val = load i8, ptr %ig.ptr, align 1 + %cmp.ext = zext i8 %ig.val to i64 + %icmpN = icmp eq i64 %cmp.ext, 123 + br i1 %icmpN, label %if.then.7, label %if.else.8 + +while.exit.6: ; preds = %while.hdr.4 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %icmpN = icmp slt i64 %loadN, %lenN + br i1 %icmpN, label %if.then.40, label %if.merge.41 + +if.then.7: ; preds = %while.body.5 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %icmpN = icmp slt i64 %add, %lenN + br i1 %icmpN, label %if.then.10, label %if.else.11 + +if.else.8: ; preds = %while.body.5 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.data19 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr20 = getelementptr i8, ptr %ig.data19, i64 %loadN + %ig.val21 = load i8, ptr %ig.ptr20, align 1 + %cmp.ext22 = zext i8 %ig.val21 to i64 + %icmpN = icmp eq i64 %cmp.ext22, 125 + br i1 %icmpN, label %if.then.31, label %if.else.32 + +if.merge.9: ; preds = %if.merge.33, %if.merge.12 + br label %while.hdr.4 + +if.then.10: ; preds = %if.then.7 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %ig.data27 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr28 = getelementptr i8, ptr %ig.data27, i64 %addN + %ig.val29 = load i8, ptr %ig.ptr28, align 1 + %cmp.ext30 = zext i8 %ig.val29 to i64 + %icmpN = icmp eq i64 %cmp.ext30, 125 + br i1 %icmpN, label %if.then.13, label %if.else.14 + +if.else.11: ; preds = %if.then.7 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.12 + +if.merge.12: ; preds = %if.merge.15, %if.else.11 + br label %if.merge.9 + +if.then.13: ; preds = %if.then.10 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, %loadN + br i1 %icmpN, label %if.then.16, label %if.merge.17 + +if.else.14: ; preds = %if.then.10 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %ig.data40 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr41 = getelementptr i8, ptr %ig.data40, i64 %addN + %ig.val42 = load i8, ptr %ig.ptr41, align 1 + %cmp.ext43 = zext i8 %ig.val42 to i64 + %icmpN = icmp eq i64 %cmp.ext43, 123 + br i1 %icmpN, label %if.then.28, label %if.else.29 + +if.merge.15: ; preds = %if.merge.30, %if.merge.17 + br label %if.merge.12 + +if.then.16: ; preds = %if.then.13 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.103, i64 36 }) + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.104, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %sub = sub i64 %loadN, %loadN + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %sub) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.105, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.17 + +if.merge.17: ; preds = %if.then.16, %if.then.13 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.106, i64 43 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.107, i64 5 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 2 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + store i64 %loadN, ptr %allocaN, align 8 + br label %if.merge.15 + +if.then.28: ; preds = %if.else.14 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.108, i64 36 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.109, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, %loadN + %addN = add i64 %subN, 1 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %addN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.110, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 2 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + store i64 %loadN, ptr %allocaN, align 8 + br label %if.merge.30 + +if.else.29: ; preds = %if.else.14 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.30 + +if.merge.30: ; preds = %if.else.29, %if.then.28 + br label %if.merge.15 + +if.then.31: ; preds = %if.else.8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %icmpN = icmp slt i64 %addN, %lenN + br i1 %icmpN, label %if.then.34, label %if.else.35 + +if.else.32: ; preds = %if.else.8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.33 + +if.merge.33: ; preds = %if.merge.36, %if.else.32 + br label %if.merge.9 + +if.then.34: ; preds = %if.then.31 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + %ig.data104 = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr105 = getelementptr i8, ptr %ig.data104, i64 %addN + %ig.val106 = load i8, ptr %ig.ptr105, align 1 + %cmp.ext107 = zext i8 %ig.val106 to i64 + %icmpN = icmp eq i64 %cmp.ext107, 125 + br i1 %icmpN, label %if.then.37, label %if.else.38 + +if.else.35: ; preds = %if.then.31 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.36 + +if.merge.36: ; preds = %if.merge.39, %if.else.35 + br label %if.merge.33 + +if.then.37: ; preds = %if.then.34 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.111, i64 36 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.112, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %loadN, %loadN + %addN = add i64 %subN, 1 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %addN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.113, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 2 + store i64 %addN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + store i64 %loadN, ptr %allocaN, align 8 + br label %if.merge.39 + +if.else.38: ; preds = %if.then.34 + %loadN = load i64, ptr %allocaN, align 8 + %addN = add i64 %loadN, 1 + store i64 %addN, ptr %allocaN, align 8 + br label %if.merge.39 + +if.merge.39: ; preds = %if.else.38, %if.then.37 + br label %if.merge.36 + +if.then.40: ; preds = %while.exit.6 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.114, i64 36 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.115, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %lenN = extractvalue { ptr, i64 } %loadN, 1 + %loadN = load i64, ptr %allocaN, align 8 + %subN = sub i64 %lenN, %loadN + %callN = call { ptr, i64 } @int_to_string(ptr %0, i64 %subN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.116, i64 4 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.41 + +if.merge.41: ; preds = %if.then.40, %while.exit.6 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +declare void @BuildOptions.add_link_flag(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.add_framework(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_output_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_wasm_shell(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.add_asset_dir(i64, ptr, ptr) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.asset_dir_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.asset_dir_src_at(i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.asset_dir_dest_at(i64, i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_post_link_callback(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_post_link_module(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.binary_path(i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_bundle_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_bundle_id(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_codesign_identity(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_provisioning_profile(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.bundle_path(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.bundle_id(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.codesign_identity(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.provisioning_profile(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.target_triple(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_macos(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios_device(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios_simulator(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_android(i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.framework_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.framework_at(i64, i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.framework_path_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.framework_path_at(i64, i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_manifest_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_keystore_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.manifest_path(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.keystore_path(i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.jni_main_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.jni_main_foreign_path_at(i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0 + +; Function Attrs: nounwind +declare i64 @build_options() #0 + +; Function Attrs: nounwind +declare ptr @objc_getClass(ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_lookUpClass(ptr) #0 + +; Function Attrs: nounwind +declare ptr @sel_registerName(ptr) #0 + +; Function Attrs: nounwind +declare ptr @class_createInstance(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @object_getClass(ptr) #0 + +; Function Attrs: nounwind +declare ptr @object_getIvar(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @object_setIvar(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_msgSend(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_allocateClassPair(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare i1 @class_addMethod(ptr, ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare i1 @class_addProtocol(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_getProtocol(ptr) #0 + +; Function Attrs: nounwind +declare void @objc_registerClassPair(ptr) #0 + +; Function Attrs: nounwind +declare void @NSLog(ptr) #0 + +; Function Attrs: nounwind +declare ptr @objc_autoreleasePoolPush() #0 + +; Function Attrs: nounwind +declare void @objc_autoreleasePoolPop(ptr) #0 + +; Function Attrs: nounwind +declare void @autoreleasepool(ptr, [2 x i64]) #0 + +; Function Attrs: nounwind +declare ptr @class_getInstanceMethod(ptr, ptr) #0 + +; Function Attrs: nounwind +define i32 @main() #0 { +entry: + call void @__sx_objc_selector_init() + call void @__sx_objc_defined_class_init() + call void @__sx_objc_class_init() + %load = load ptr, ptr @OBJC_CLASSLIST_REFERENCES_SxBox, align 8 + %call = call ptr @class_createInstance(ptr %load, i64 0) + %loadN = load { { ptr, ptr, ptr }, ptr }, ptr @__sx_default_context, align 8 + %sg = extractvalue { { ptr, ptr, ptr }, ptr } %loadN, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 1 + %icall = call ptr %sgN(ptr @__sx_default_context, ptr %sgN, i64 40) + %callN = call ptr @memset(ptr %icall, i32 0, i64 40) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %icall, i32 0, i32 0 + store { ptr, ptr, ptr } %sg, ptr %gep, align 8 + %loadN = load ptr, ptr @__SxBox_state_ivar, align 8 + call void @object_setIvar(ptr %call, ptr %loadN, ptr %icall) + %loadN = load ptr, ptr @OBJC_SELECTOR_REFERENCES_init, align 8 + %objc.msg = call ptr @objc_msgSend(ptr %call, ptr %loadN) + %alloca = alloca ptr, align 8 + store ptr %objc.msg, ptr %alloca, align 8 + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr @OBJC_SELECTOR_REFERENCES_setWidth_, align 8 + call void @objc_msgSend(ptr %loadN, ptr %loadN, i32 10) + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr @OBJC_SELECTOR_REFERENCES_setHeight_, align 8 + call void @objc_msgSend(ptr %loadN, ptr %loadN, i32 7) + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr @OBJC_SELECTOR_REFERENCES_width, align 8 + %objc.msg13 = call i32 @objc_msgSend(ptr %loadN, ptr %loadN) + %allocaN = alloca i32, align 4 + store i32 %objc.msg13, ptr %allocaN, align 4 + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr @OBJC_SELECTOR_REFERENCES_height, align 8 + %objc.msg17 = call i32 @objc_msgSend(ptr %loadN, ptr %loadN) + %allocaN = alloca i32, align 4 + store i32 %objc.msg17, ptr %allocaN, align 4 + %loadN = load i32, ptr %allocaN, align 4 + %cmp.ext = sext i32 %loadN to i64 + %icmp = icmp ne i64 %cmp.ext, 10 + br i1 %icmp, label %or.merge.1, label %or.rhs.0 + +or.rhs.0: ; preds = %entry + %loadN = load i32, ptr %allocaN, align 4 + %cmp.ext21 = sext i32 %loadN to i64 + %icmpN = icmp ne i64 %cmp.ext21, 7 + br label %or.merge.1 + +or.merge.1: ; preds = %or.rhs.0, %entry + %bp = phi i1 [ true, %entry ], [ %icmpN, %or.rhs.0 ] + br i1 %bp, label %if.then.2, label %if.merge.3 + +if.then.2: ; preds = %or.merge.1 + call void @print__ct_s8b0209b1c8e86f29__pack(ptr @__sx_default_context) + ret i32 1 + +if.merge.3: ; preds = %or.merge.1 + %loadN = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr @OBJC_SELECTOR_REFERENCES_area, align 8 + %objc.msg25 = call i32 @objc_msgSend(ptr %loadN, ptr %loadN) + %allocaN = alloca i32, align 4 + store i32 %objc.msg25, ptr %allocaN, align 4 + %loadN = load i32, ptr %allocaN, align 4 + %cmp.ext28 = sext i32 %loadN to i64 + %icmpN = icmp ne i64 %cmp.ext28, 0 + br i1 %icmpN, label %if.then.42, label %if.merge.43 + +if.then.42: ; preds = %if.merge.3 + %loadN = load i32, ptr %allocaN, align 4 + call void @print__ct_s39d1c95f7ad02cfe__pack_s32(ptr @__sx_default_context, i32 %loadN) + ret i32 1 + +if.merge.43: ; preds = %if.merge.3 + %allocaN = alloca ptr, align 8 + %callN = call ptr @objc_getClass(ptr @str.117) + store ptr %callN, ptr %allocaN, align 8 + %allocaN = alloca ptr, align 8 + %callN = call ptr @sel_registerName(ptr @str.118) + store ptr %callN, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %callN = call ptr @class_getInstanceMethod(ptr %loadN, ptr %loadN) + %allocaN = alloca ptr, align 8 + store ptr %callN, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %icmpN = icmp ne ptr %loadN, null + br i1 %icmpN, label %if.then.44, label %if.merge.45 + +if.then.44: ; preds = %if.merge.43 + call void @print__ct_sa76620ef481aaa22__pack(ptr @__sx_default_context) + ret i32 1 + +if.merge.45: ; preds = %if.merge.43 + %loadN = load i32, ptr %allocaN, align 4 + %loadN = load i32, ptr %allocaN, align 4 + %loadN = load i32, ptr %allocaN, align 4 + call void @print__ct_se44eb1a972327e7d__pack_s32_s32_s32(ptr @__sx_default_context, i32 %loadN, i32 %loadN, i32 %loadN) + ret i32 0 +} + +; Function Attrs: nounwind +define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %call = call ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + call void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) + ret void +} + +; Function Attrs: nounwind +define internal void @print__ct_s8b0209b1c8e86f29__pack(ptr %0) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.119, i64 30 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr null, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 0, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.120, i64 0 }, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 30) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %load, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %1 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_0(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.121, i64 30 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal void @print__ct_s39d1c95f7ad02cfe__pack_s32(ptr %0, i32 %1) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.122, i64 30 }, ptr %alloca, align 8 + %allocaN = alloca i32, align 4 + store i32 %1, ptr %allocaN, align 4 + %allocaN = alloca [1 x { i64, i64 }], align 8 + %load = load i32, ptr %allocaN, align 4 + %sN = sext i32 %load to i64 + %ba.val = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %igp.ptr = getelementptr { i64, i64 }, ptr %allocaN, i64 0 + store { i64, i64 } %ba.val, ptr %igp.ptr, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %igp.ptr4 = getelementptr { i64, i64 }, ptr %allocaN, i64 0 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr %igp.ptr4, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 1, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.123, i64 0 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 27) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i32, ptr %allocaN, align 4 + %sN = sext i32 %loadN to i64 + %ba.val13 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val13) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %callN = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 29, i64 1) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %2 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_1(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.124, i64 30 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal void @print__ct_sa76620ef481aaa22__pack(ptr %0) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.125, i64 51 }, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr null, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 0, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.126, i64 0 }, ptr %allocaN, align 8 + %load = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 51) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %load, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %1 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_2(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.127, i64 51 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal void @print__ct_se44eb1a972327e7d__pack_s32_s32_s32(ptr %0, i32 %1, i32 %2, i32 %3) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.128, i64 28 }, ptr %alloca, align 8 + %allocaN = alloca i32, align 4 + store i32 %1, ptr %allocaN, align 4 + %allocaN = alloca i32, align 4 + store i32 %2, ptr %allocaN, align 4 + %allocaN = alloca i32, align 4 + store i32 %3, ptr %allocaN, align 4 + %allocaN = alloca [3 x { i64, i64 }], align 8 + %load = load i32, ptr %allocaN, align 4 + %sN = sext i32 %load to i64 + %ba.val = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %igp.ptr = getelementptr { i64, i64 }, ptr %allocaN, i64 0 + store { i64, i64 } %ba.val, ptr %igp.ptr, align 8 + %loadN = load i32, ptr %allocaN, align 4 + %sN = sext i32 %loadN to i64 + %ba.val7 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %igp.ptr8 = getelementptr { i64, i64 }, ptr %allocaN, i64 1 + store { i64, i64 } %ba.val7, ptr %igp.ptr8, align 8 + %loadN = load i32, ptr %allocaN, align 4 + %sN = sext i32 %loadN to i64 + %ba.val11 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %igp.ptr12 = getelementptr { i64, i64 }, ptr %allocaN, i64 2 + store { i64, i64 } %ba.val11, ptr %igp.ptr12, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + %igp.ptr14 = getelementptr { i64, i64 }, ptr %allocaN, i64 0 + %gep = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 0 + store ptr %igp.ptr14, ptr %gep, align 8 + %gepN = getelementptr inbounds { ptr, i64 }, ptr %allocaN, i32 0, i32 1 + store i64 3, ptr %gepN, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.129, i64 0 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %call = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 0, i64 12) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %call) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i32, ptr %allocaN, align 4 + %sN = sext i32 %loadN to i64 + %ba.val23 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val23) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %callN = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 14, i64 3) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i32, ptr %allocaN, align 4 + %sN = sext i32 %loadN to i64 + %ba.val33 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val33) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %callN = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 19, i64 6) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i32, ptr %allocaN, align 4 + %sN = sext i32 %loadN to i64 + %ba.val43 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val43) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %callN = call { ptr, i64 } @substr(ptr %0, { ptr, i64 } %loadN, i64 27, i64 1) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %str.ptr = extractvalue { ptr, i64 } %loadN, 0 + %str.len = extractvalue { ptr, i64 } %loadN, 1 + %4 = call i64 @write(i32 1, ptr %str.ptr, i64 %str.len) + ret void +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @__insert_3(ptr %0) #0 { +entry: + %call = call { ptr, i64 } @build_format(ptr %0, { ptr, i64 } { ptr @str.130, i64 28 }) + ret { ptr, i64 } %call +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__Source_Location(ptr %0, { { ptr, i64 }, i32, i32, { ptr, i64 } } %1) #0 { +entry: + %alloca = alloca { { ptr, i64 }, i32, i32, { ptr, i64 } }, align 8 + store { { ptr, i64 }, i32, i32, { ptr, i64 } } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.131, i64 15 }, { ptr, i64 } { ptr @str.132, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.93 + +while.hdr.93: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 4 + br i1 %icmp, label %while.body.94, label %while.exit.95 + +while.body.94: ; preds = %while.hdr.93 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.96, label %if.merge.97 + +while.exit.95: ; preds = %while.hdr.93 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.133, i64 1 }) + ret { ptr, i64 } %callN + +if.then.96: ; preds = %while.body.94 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.134, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.97 + +if.merge.97: ; preds = %if.then.96, %while.body.94 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [4 x { ptr, i64 }], ptr @field_names, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.138, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { { ptr, i64 }, i32, i32, { ptr, i64 } }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + i64 2, label %fv.case20 + i64 3, label %fv.case24 + ] + +fv.merge: ; preds = %fv.default, %fv.case24, %fv.case20, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val19, %fv.case17 ], [ %fv.val23, %fv.case20 ], [ %fv.val28, %fv.case24 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.93 + +fv.default: ; preds = %if.merge.97 + br label %fv.merge + +fv.case: ; preds = %if.merge.97 + %fv.field = extractvalue { { ptr, i64 }, i32, i32, { ptr, i64 } } %loadN, 0 + %ba.tmp = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %fv.field, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %fv.val = insertvalue { i64, i64 } { i64 12, i64 undef }, i64 %ba.p2i, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.97 + %fv.field18 = extractvalue { { ptr, i64 }, i32, i32, { ptr, i64 } } %loadN, 1 + %sN = sext i32 %fv.field18 to i64 + %fv.val19 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + br label %fv.merge + +fv.case20: ; preds = %if.merge.97 + %fv.field21 = extractvalue { { ptr, i64 }, i32, i32, { ptr, i64 } } %loadN, 2 + %sN = sext i32 %fv.field21 to i64 + %fv.val23 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + br label %fv.merge + +fv.case24: ; preds = %if.merge.97 + %fv.field25 = extractvalue { { ptr, i64 }, i32, i32, { ptr, i64 } } %loadN, 3 + %ba.tmp26 = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %fv.field25, ptr %ba.tmp26, align 8 + %ba.p2i27 = ptrtoint ptr %ba.tmp26 to i64 + %fv.val28 = insertvalue { i64, i64 } { i64 12, i64 undef }, i64 %ba.p2i27, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__Allocator(ptr %0, { ptr, ptr, ptr } %1) #0 { +entry: + %alloca = alloca { ptr, ptr, ptr }, align 8 + store { ptr, ptr, ptr } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.139, i64 9 }, { ptr, i64 } { ptr @str.140, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.98 + +while.hdr.98: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 3 + br i1 %icmp, label %while.body.99, label %while.exit.100 + +while.body.99: ; preds = %while.hdr.98 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.101, label %if.merge.102 + +while.exit.100: ; preds = %while.hdr.98 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.141, i64 1 }) + ret { ptr, i64 } %callN + +if.then.101: ; preds = %while.body.99 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.142, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.102 + +if.merge.102: ; preds = %if.then.101, %while.body.99 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [3 x { ptr, i64 }], ptr @field_names.146, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.147, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, ptr, ptr }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + i64 2, label %fv.case21 + ] + +fv.merge: ; preds = %fv.default, %fv.case21, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val20, %fv.case17 ], [ %fv.val24, %fv.case21 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.98 + +fv.default: ; preds = %if.merge.102 + br label %fv.merge + +fv.case: ; preds = %if.merge.102 + %fv.field = extractvalue { ptr, ptr, ptr } %loadN, 0 + %pNi = ptrtoint ptr %fv.field to i64 + %fv.val = insertvalue { i64, i64 } { i64 18, i64 undef }, i64 %pNi, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.102 + %fv.field18 = extractvalue { ptr, ptr, ptr } %loadN, 1 + %pNi19 = ptrtoint ptr %fv.field18 to i64 + %fv.val20 = insertvalue { i64, i64 } { i64 18, i64 undef }, i64 %pNi19, 1 + br label %fv.merge + +fv.case21: ; preds = %if.merge.102 + %fv.field22 = extractvalue { ptr, ptr, ptr } %loadN, 2 + %pNi23 = ptrtoint ptr %fv.field22 to i64 + %fv.val24 = insertvalue { i64, i64 } { i64 18, i64 undef }, i64 %pNi23, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__CAllocator(ptr %0, {} %1) #0 { +entry: + %alloca = alloca {}, align 8 + store {} %1, ptr %alloca, align 1 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.148, i64 10 }, { ptr, i64 } { ptr @str.149, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.103 + +while.hdr.103: ; preds = %if.merge.107, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 0 + br i1 %icmp, label %while.body.104, label %while.exit.105 + +while.body.104: ; preds = %while.hdr.103 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.106, label %if.merge.107 + +while.exit.105: ; preds = %while.hdr.103 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.150, i64 1 }) + ret { ptr, i64 } %callN + +if.then.106: ; preds = %while.body.104 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.151, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.107 + +if.merge.107: ; preds = %if.then.106, %while.body.104 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [0 x { ptr, i64 }], ptr @field_names.152, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.153, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load {}, ptr %alloca, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } { i64 17, i64 0 }) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.103 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__GPA(ptr %0, { i64 } %1) #0 { +entry: + %alloca = alloca { i64 }, align 8 + store { i64 } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.154, i64 3 }, { ptr, i64 } { ptr @str.155, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.108 + +while.hdr.108: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 1 + br i1 %icmp, label %while.body.109, label %while.exit.110 + +while.body.109: ; preds = %while.hdr.108 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.111, label %if.merge.112 + +while.exit.110: ; preds = %while.hdr.108 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.156, i64 1 }) + ret { ptr, i64 } %callN + +if.then.111: ; preds = %while.body.109 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.157, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.112 + +if.merge.112: ; preds = %if.then.111, %while.body.109 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [1 x { ptr, i64 }], ptr @field_names.159, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.160, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + ] + +fv.merge: ; preds = %fv.default, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.108 + +fv.default: ; preds = %if.merge.112 + br label %fv.merge + +fv.case: ; preds = %if.merge.112 + %fv.field = extractvalue { i64 } %loadN, 0 + %fv.val = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__ArenaChunk(ptr %0, { ptr, i64 } %1) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.161, i64 10 }, { ptr, i64 } { ptr @str.162, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.113 + +while.hdr.113: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 2 + br i1 %icmp, label %while.body.114, label %while.exit.115 + +while.body.114: ; preds = %while.hdr.113 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.116, label %if.merge.117 + +while.exit.115: ; preds = %while.hdr.113 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.163, i64 1 }) + ret { ptr, i64 } %callN + +if.then.116: ; preds = %while.body.114 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.164, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.117 + +if.merge.117: ; preds = %if.then.116, %while.body.114 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [2 x { ptr, i64 }], ptr @field_names.167, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.168, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + ] + +fv.merge: ; preds = %fv.default, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val19, %fv.case17 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.113 + +fv.default: ; preds = %if.merge.117 + br label %fv.merge + +fv.case: ; preds = %if.merge.117 + %fv.field = extractvalue { ptr, i64 } %loadN, 0 + %pNi = ptrtoint ptr %fv.field to i64 + %fv.val = insertvalue { i64, i64 } { i64 26, i64 undef }, i64 %pNi, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.117 + %fv.field18 = extractvalue { ptr, i64 } %loadN, 1 + %fv.val19 = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field18, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__Arena(ptr %0, { ptr, i64, { ptr, ptr, ptr } } %1) #0 { +entry: + %alloca = alloca { ptr, i64, { ptr, ptr, ptr } }, align 8 + store { ptr, i64, { ptr, ptr, ptr } } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.169, i64 5 }, { ptr, i64 } { ptr @str.170, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.118 + +while.hdr.118: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 3 + br i1 %icmp, label %while.body.119, label %while.exit.120 + +while.body.119: ; preds = %while.hdr.118 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.121, label %if.merge.122 + +while.exit.120: ; preds = %while.hdr.118 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.171, i64 1 }) + ret { ptr, i64 } %callN + +if.then.121: ; preds = %while.body.119 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.172, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.122 + +if.merge.122: ; preds = %if.then.121, %while.body.119 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [3 x { ptr, i64 }], ptr @field_names.176, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.177, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64, { ptr, ptr, ptr } }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + i64 2, label %fv.case20 + ] + +fv.merge: ; preds = %fv.default, %fv.case20, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val19, %fv.case17 ], [ %fv.val22, %fv.case20 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.118 + +fv.default: ; preds = %if.merge.122 + br label %fv.merge + +fv.case: ; preds = %if.merge.122 + %fv.field = extractvalue { ptr, i64, { ptr, ptr, ptr } } %loadN, 0 + %pNi = ptrtoint ptr %fv.field to i64 + %fv.val = insertvalue { i64, i64 } { i64 26, i64 undef }, i64 %pNi, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.122 + %fv.field18 = extractvalue { ptr, i64, { ptr, ptr, ptr } } %loadN, 1 + %fv.val19 = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field18, 1 + br label %fv.merge + +fv.case20: ; preds = %if.merge.122 + %fv.field21 = extractvalue { ptr, i64, { ptr, ptr, ptr } } %loadN, 2 + %ba.tmp = alloca { ptr, ptr, ptr }, align 8 + store { ptr, ptr, ptr } %fv.field21, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %fv.val22 = insertvalue { i64, i64 } { i64 20, i64 undef }, i64 %ba.p2i, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__BufAlloc(ptr %0, { ptr, i64, i64 } %1) #0 { +entry: + %alloca = alloca { ptr, i64, i64 }, align 8 + store { ptr, i64, i64 } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.178, i64 8 }, { ptr, i64 } { ptr @str.179, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.123 + +while.hdr.123: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 3 + br i1 %icmp, label %while.body.124, label %while.exit.125 + +while.body.124: ; preds = %while.hdr.123 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.126, label %if.merge.127 + +while.exit.125: ; preds = %while.hdr.123 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.180, i64 1 }) + ret { ptr, i64 } %callN + +if.then.126: ; preds = %while.body.124 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.181, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.127 + +if.merge.127: ; preds = %if.then.126, %while.body.124 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [3 x { ptr, i64 }], ptr @field_names.185, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.186, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + i64 2, label %fv.case20 + ] + +fv.merge: ; preds = %fv.default, %fv.case20, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val19, %fv.case17 ], [ %fv.val22, %fv.case20 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.123 + +fv.default: ; preds = %if.merge.127 + br label %fv.merge + +fv.case: ; preds = %if.merge.127 + %fv.field = extractvalue { ptr, i64, i64 } %loadN, 0 + %pNi = ptrtoint ptr %fv.field to i64 + %fv.val = insertvalue { i64, i64 } { i64 29, i64 undef }, i64 %pNi, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.127 + %fv.field18 = extractvalue { ptr, i64, i64 } %loadN, 1 + %fv.val19 = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field18, 1 + br label %fv.merge + +fv.case20: ; preds = %if.merge.127 + %fv.field21 = extractvalue { ptr, i64, i64 } %loadN, 2 + %fv.val22 = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field21, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__TrackingAllocator(ptr %0, { { ptr, ptr, ptr }, i64, i64, i64 } %1) #0 { +entry: + %alloca = alloca { { ptr, ptr, ptr }, i64, i64, i64 }, align 8 + store { { ptr, ptr, ptr }, i64, i64, i64 } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.187, i64 17 }, { ptr, i64 } { ptr @str.188, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.128 + +while.hdr.128: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 4 + br i1 %icmp, label %while.body.129, label %while.exit.130 + +while.body.129: ; preds = %while.hdr.128 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.131, label %if.merge.132 + +while.exit.130: ; preds = %while.hdr.128 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.189, i64 1 }) + ret { ptr, i64 } %callN + +if.then.131: ; preds = %while.body.129 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.190, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.132 + +if.merge.132: ; preds = %if.then.131, %while.body.129 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [4 x { ptr, i64 }], ptr @field_names.195, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.196, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { { ptr, ptr, ptr }, i64, i64, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + i64 2, label %fv.case20 + i64 3, label %fv.case23 + ] + +fv.merge: ; preds = %fv.default, %fv.case23, %fv.case20, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val19, %fv.case17 ], [ %fv.val22, %fv.case20 ], [ %fv.val25, %fv.case23 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.128 + +fv.default: ; preds = %if.merge.132 + br label %fv.merge + +fv.case: ; preds = %if.merge.132 + %fv.field = extractvalue { { ptr, ptr, ptr }, i64, i64, i64 } %loadN, 0 + %ba.tmp = alloca { ptr, ptr, ptr }, align 8 + store { ptr, ptr, ptr } %fv.field, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %fv.val = insertvalue { i64, i64 } { i64 20, i64 undef }, i64 %ba.p2i, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.132 + %fv.field18 = extractvalue { { ptr, ptr, ptr }, i64, i64, i64 } %loadN, 1 + %fv.val19 = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field18, 1 + br label %fv.merge + +fv.case20: ; preds = %if.merge.132 + %fv.field21 = extractvalue { { ptr, ptr, ptr }, i64, i64, i64 } %loadN, 2 + %fv.val22 = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field21, 1 + br label %fv.merge + +fv.case23: ; preds = %if.merge.132 + %fv.field24 = extractvalue { { ptr, ptr, ptr }, i64, i64, i64 } %loadN, 3 + %fv.val25 = insertvalue { i64, i64 } { i64 5, i64 undef }, i64 %fv.field24, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__Context(ptr %0, { { ptr, ptr, ptr }, ptr } %1) #0 { +entry: + %alloca = alloca { { ptr, ptr, ptr }, ptr }, align 8 + store { { ptr, ptr, ptr }, ptr } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.197, i64 7 }, { ptr, i64 } { ptr @str.198, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.133 + +while.hdr.133: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 2 + br i1 %icmp, label %while.body.134, label %while.exit.135 + +while.body.134: ; preds = %while.hdr.133 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.136, label %if.merge.137 + +while.exit.135: ; preds = %while.hdr.133 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.199, i64 1 }) + ret { ptr, i64 } %callN + +if.then.136: ; preds = %while.body.134 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.200, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.137 + +if.merge.137: ; preds = %if.then.136, %while.body.134 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [2 x { ptr, i64 }], ptr @field_names.203, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.204, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { { ptr, ptr, ptr }, ptr }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + ] + +fv.merge: ; preds = %fv.default, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val19, %fv.case17 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.133 + +fv.default: ; preds = %if.merge.137 + br label %fv.merge + +fv.case: ; preds = %if.merge.137 + %fv.field = extractvalue { { ptr, ptr, ptr }, ptr } %loadN, 0 + %ba.tmp = alloca { ptr, ptr, ptr }, align 8 + store { ptr, ptr, ptr } %fv.field, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %fv.val = insertvalue { i64, i64 } { i64 20, i64 undef }, i64 %ba.p2i, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.137 + %fv.field18 = extractvalue { { ptr, ptr, ptr }, ptr } %loadN, 1 + %pNi = ptrtoint ptr %fv.field18 to i64 + %fv.val19 = insertvalue { i64, i64 } { i64 18, i64 undef }, i64 %pNi, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__BuildOptions(ptr %0, {} %1) #0 { +entry: + %alloca = alloca {}, align 8 + store {} %1, ptr %alloca, align 1 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.205, i64 12 }, { ptr, i64 } { ptr @str.206, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.138 + +while.hdr.138: ; preds = %if.merge.142, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 0 + br i1 %icmp, label %while.body.139, label %while.exit.140 + +while.body.139: ; preds = %while.hdr.138 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.141, label %if.merge.142 + +while.exit.140: ; preds = %while.hdr.138 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.207, i64 1 }) + ret { ptr, i64 } %callN + +if.then.141: ; preds = %while.body.139 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.208, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.142 + +if.merge.142: ; preds = %if.then.141, %while.body.139 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [0 x { ptr, i64 }], ptr @field_names.209, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.210, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load {}, ptr %alloca, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } { i64 17, i64 0 }) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.138 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__NSString(ptr %0, {} %1) #0 { +entry: + %alloca = alloca {}, align 8 + store {} %1, ptr %alloca, align 1 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.211, i64 8 }, { ptr, i64 } { ptr @str.212, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.143 + +while.hdr.143: ; preds = %if.merge.147, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 0 + br i1 %icmp, label %while.body.144, label %while.exit.145 + +while.body.144: ; preds = %while.hdr.143 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.146, label %if.merge.147 + +while.exit.145: ; preds = %while.hdr.143 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.213, i64 1 }) + ret { ptr, i64 } %callN + +if.then.146: ; preds = %while.body.144 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.214, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.147 + +if.merge.147: ; preds = %if.then.146, %while.body.144 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [0 x { ptr, i64 }], ptr @field_names.215, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.216, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load {}, ptr %alloca, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } { i64 17, i64 0 }) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.143 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string__SxBox(ptr %0, {} %1) #0 { +entry: + %alloca = alloca {}, align 8 + store {} %1, ptr %alloca, align 1 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.217, i64 5 }, { ptr, i64 } { ptr @str.218, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.148 + +while.hdr.148: ; preds = %if.merge.152, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 0 + br i1 %icmp, label %while.body.149, label %while.exit.150 + +while.body.149: ; preds = %while.hdr.148 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.151, label %if.merge.152 + +while.exit.150: ; preds = %while.hdr.148 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.219, i64 1 }) + ret { ptr, i64 } %callN + +if.then.151: ; preds = %while.body.149 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.220, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.152 + +if.merge.152: ; preds = %if.then.151, %while.body.149 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [0 x { ptr, i64 }], ptr @field_names.221, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.222, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load {}, ptr %alloca, align 1 + %loadN = load i64, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } { i64 17, i64 0 }) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.148 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @struct_to_string____SxBoxState(ptr %0, { { ptr, ptr, ptr }, i32, i32, i32 } %1) #0 { +entry: + %alloca = alloca { { ptr, ptr, ptr }, i32, i32, i32 }, align 8 + store { { ptr, ptr, ptr }, i32, i32, i32 } %1, ptr %alloca, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.223, i64 12 }, { ptr, i64 } { ptr @str.224, i64 1 }) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.153 + +while.hdr.153: ; preds = %fv.merge, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 4 + br i1 %icmp, label %while.body.154, label %while.exit.155 + +while.body.154: ; preds = %while.hdr.153 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.156, label %if.merge.157 + +while.exit.155: ; preds = %while.hdr.153 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.225, i64 1 }) + ret { ptr, i64 } %callN + +if.then.156: ; preds = %while.body.154 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.226, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.157 + +if.merge.157: ; preds = %if.then.156, %while.body.154 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [4 x { ptr, i64 }], ptr @field_names.231, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %fn.load) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.232, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + switch i64 %loadN, label %fv.default [ + i64 0, label %fv.case + i64 1, label %fv.case17 + i64 2, label %fv.case20 + i64 3, label %fv.case24 + ] + +fv.merge: ; preds = %fv.default, %fv.case24, %fv.case20, %fv.case17, %fv.case + %fv.phi = phi { i64, i64 } [ %fv.val, %fv.case ], [ %fv.val19, %fv.case17 ], [ %fv.val23, %fv.case20 ], [ %fv.val27, %fv.case24 ], [ undef, %fv.default ] + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %fv.phi) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.153 + +fv.default: ; preds = %if.merge.157 + br label %fv.merge + +fv.case: ; preds = %if.merge.157 + %fv.field = extractvalue { { ptr, ptr, ptr }, i32, i32, i32 } %loadN, 0 + %ba.tmp = alloca { ptr, ptr, ptr }, align 8 + store { ptr, ptr, ptr } %fv.field, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %fv.val = insertvalue { i64, i64 } { i64 20, i64 undef }, i64 %ba.p2i, 1 + br label %fv.merge + +fv.case17: ; preds = %if.merge.157 + %fv.field18 = extractvalue { { ptr, ptr, ptr }, i32, i32, i32 } %loadN, 1 + %sN = sext i32 %fv.field18 to i64 + %fv.val19 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + br label %fv.merge + +fv.case20: ; preds = %if.merge.157 + %fv.field21 = extractvalue { { ptr, ptr, ptr }, i32, i32, i32 } %loadN, 2 + %sN = sext i32 %fv.field21 to i64 + %fv.val23 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + br label %fv.merge + +fv.case24: ; preds = %if.merge.157 + %fv.field25 = extractvalue { { ptr, ptr, ptr }, i32, i32, i32 } %loadN, 3 + %sN = sext i32 %fv.field25 to i64 + %fv.val27 = insertvalue { i64, i64 } { i64 4, i64 undef }, i64 %sN, 1 + br label %fv.merge +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @enum_to_string__OperatingSystem(ptr %0, i64 %1) #0 { +entry: + %alloca = alloca i64, align 8 + store i64 %1, ptr %alloca, align 8 + %load = load i64, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %load, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [7 x { ptr, i64 }], ptr @field_names.241, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.233, i64 1 }, { ptr, i64 } %fn.load) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load i64, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %allocaN = alloca { i64, i64 }, align 8 + store { i64, i64 } { i64 17, i64 0 }, ptr %allocaN, align 8 + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %loadN) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %len = extractvalue { ptr, i64 } %loadN, 1 + %icmp = icmp sgt i64 %len, 0 + br i1 %icmp, label %if.then.162, label %if.merge.163 + +if.then.162: ; preds = %entry + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.243, i64 1 }) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.242, i64 1 }, { ptr, i64 } %callN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.163 + +if.merge.163: ; preds = %if.then.162, %entry + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @enum_to_string__Architecture(ptr %0, i64 %1) #0 { +entry: + %alloca = alloca i64, align 8 + store i64 %1, ptr %alloca, align 8 + %load = load i64, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %load, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %fn.gep = getelementptr inbounds [5 x { ptr, i64 }], ptr @field_names.250, i64 0, i64 %loadN + %fn.load = load { ptr, i64 }, ptr %fn.gep, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.244, i64 1 }, { ptr, i64 } %fn.load) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %call, ptr %allocaN, align 8 + %loadN = load i64, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %allocaN = alloca { i64, i64 }, align 8 + store { i64, i64 } { i64 17, i64 0 }, ptr %allocaN, align 8 + %loadN = load { i64, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %loadN) + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %len = extractvalue { ptr, i64 } %loadN, 1 + %icmp = icmp sgt i64 %len, 0 + br i1 %icmp, label %if.then.164, label %if.merge.165 + +if.then.164: ; preds = %entry + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.252, i64 1 }) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.251, i64 1 }, { ptr, i64 } %callN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.165 + +if.merge.165: ; preds = %if.then.164, %entry + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + ret { ptr, i64 } %loadN +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @array_to_string__AR_1_Any(ptr %0, [1 x { i64, i64 }] %1) #0 { +entry: + %alloca = alloca [1 x { i64, i64 }], align 8 + store [1 x { i64, i64 }] %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.253, i64 1 }, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.170 + +while.hdr.170: ; preds = %if.merge.174, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 1 + br i1 %icmp, label %while.body.171, label %while.exit.172 + +while.body.171: ; preds = %while.hdr.170 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.173, label %if.merge.174 + +while.exit.172: ; preds = %while.hdr.170 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.254, i64 1 }) + ret { ptr, i64 } %call + +if.then.173: ; preds = %while.body.171 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.255, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.174 + +if.merge.174: ; preds = %if.then.173, %while.body.171 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load [1 x { i64, i64 }], ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.tmp = alloca [1 x { i64, i64 }], align 8 + store [1 x { i64, i64 }] %loadN, ptr %ig.tmp, align 8 + %ig.ptr = getelementptr [1 x { i64, i64 }], ptr %ig.tmp, i64 0, i64 %loadN + %ig.val = load { i64, i64 }, ptr %ig.ptr, align 8 + %ba.tmp = alloca { i64, i64 }, align 8 + store { i64, i64 } %ig.val, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %ba.val = insertvalue { i64, i64 } { i64 13, i64 undef }, i64 %ba.p2i, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.170 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @array_to_string__AR_3_Any(ptr %0, [3 x { i64, i64 }] %1) #0 { +entry: + %alloca = alloca [3 x { i64, i64 }], align 8 + store [3 x { i64, i64 }] %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.256, i64 1 }, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.175 + +while.hdr.175: ; preds = %if.merge.179, %entry + %load = load i64, ptr %allocaN, align 8 + %icmp = icmp slt i64 %load, 3 + br i1 %icmp, label %while.body.176, label %while.exit.177 + +while.body.176: ; preds = %while.hdr.175 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.178, label %if.merge.179 + +while.exit.177: ; preds = %while.hdr.175 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.257, i64 1 }) + ret { ptr, i64 } %call + +if.then.178: ; preds = %while.body.176 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.258, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.179 + +if.merge.179: ; preds = %if.then.178, %while.body.176 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load [3 x { i64, i64 }], ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.tmp = alloca [3 x { i64, i64 }], align 8 + store [3 x { i64, i64 }] %loadN, ptr %ig.tmp, align 8 + %ig.ptr = getelementptr [3 x { i64, i64 }], ptr %ig.tmp, i64 0, i64 %loadN + %ig.val = load { i64, i64 }, ptr %ig.ptr, align 8 + %ba.tmp = alloca { i64, i64 }, align 8 + store { i64, i64 } %ig.val, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %ba.val = insertvalue { i64, i64 } { i64 13, i64 undef }, i64 %ba.p2i, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.175 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @slice_to_string__string(ptr %0, { ptr, i64 } %1) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.259, i64 1 }, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.184 + +while.hdr.184: ; preds = %if.merge.188, %entry + %load = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %len = extractvalue { ptr, i64 } %loadN, 1 + %icmp = icmp slt i64 %load, %len + br i1 %icmp, label %while.body.185, label %while.exit.186 + +while.body.185: ; preds = %while.hdr.184 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.187, label %if.merge.188 + +while.exit.186: ; preds = %while.hdr.184 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.260, i64 1 }) + ret { ptr, i64 } %call + +if.then.187: ; preds = %while.body.185 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.261, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.188 + +if.merge.188: ; preds = %if.then.187, %while.body.185 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.data = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr = getelementptr { ptr, i64 }, ptr %ig.data, i64 %loadN + %ig.val = load { ptr, i64 }, ptr %ig.ptr, align 8 + %ba.tmp = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %ig.val, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %ba.val = insertvalue { i64, i64 } { i64 12, i64 undef }, i64 %ba.p2i, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.184 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @slice_to_string__Any(ptr %0, { ptr, i64 } %1) #0 { +entry: + %alloca = alloca { ptr, i64 }, align 8 + store { ptr, i64 } %1, ptr %alloca, align 8 + %allocaN = alloca { ptr, i64 }, align 8 + store { ptr, i64 } { ptr @str.262, i64 1 }, ptr %allocaN, align 8 + %allocaN = alloca i64, align 8 + store i64 0, ptr %allocaN, align 8 + br label %while.hdr.189 + +while.hdr.189: ; preds = %if.merge.193, %entry + %load = load i64, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %len = extractvalue { ptr, i64 } %loadN, 1 + %icmp = icmp slt i64 %load, %len + br i1 %icmp, label %while.body.190, label %while.exit.191 + +while.body.190: ; preds = %while.hdr.189 + %loadN = load i64, ptr %allocaN, align 8 + %icmpN = icmp sgt i64 %loadN, 0 + br i1 %icmpN, label %if.then.192, label %if.merge.193 + +while.exit.191: ; preds = %while.hdr.189 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %call = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.263, i64 1 }) + ret { ptr, i64 } %call + +if.then.192: ; preds = %while.body.190 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } { ptr @str.264, i64 2 }) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + br label %if.merge.193 + +if.merge.193: ; preds = %if.then.192, %while.body.190 + %loadN = load { ptr, i64 }, ptr %allocaN, align 8 + %loadN = load { ptr, i64 }, ptr %alloca, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %ig.data = extractvalue { ptr, i64 } %loadN, 0 + %ig.ptr = getelementptr { i64, i64 }, ptr %ig.data, i64 %loadN + %ig.val = load { i64, i64 }, ptr %ig.ptr, align 8 + %ba.tmp = alloca { i64, i64 }, align 8 + store { i64, i64 } %ig.val, ptr %ba.tmp, align 8 + %ba.p2i = ptrtoint ptr %ba.tmp to i64 + %ba.val = insertvalue { i64, i64 } { i64 13, i64 undef }, i64 %ba.p2i, 1 + %callN = call { ptr, i64 } @any_to_string(ptr %0, { i64, i64 } %ba.val) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } %loadN, { ptr, i64 } %callN) + store { ptr, i64 } %callN, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %add = add i64 %loadN, 1 + store i64 %add, ptr %allocaN, align 8 + br label %while.hdr.189 +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_void(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.218, label %if.else.219 + +if.then.218: ; preds = %entry + br label %if.merge.220 + +if.else.219: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.267, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.266, i64 5 }, { ptr, i64 } %callN) + br label %if.merge.220 + +if.merge.220: ; preds = %if.else.219, %if.then.218 + %bp = phi { ptr, i64 } [ { ptr @str.265, i64 4 }, %if.then.218 ], [ %callN, %if.else.219 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_CAllocator(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.242, label %if.else.243 + +if.then.242: ; preds = %entry + br label %if.merge.244 + +if.else.243: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.270, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.269, i64 11 }, { ptr, i64 } %callN) + br label %if.merge.244 + +if.merge.244: ; preds = %if.else.243, %if.then.242 + %bp = phi { ptr, i64 } [ { ptr @str.268, i64 4 }, %if.then.242 ], [ %callN, %if.else.243 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_GPA(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.245, label %if.else.246 + +if.then.245: ; preds = %entry + br label %if.merge.247 + +if.else.246: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.273, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.272, i64 4 }, { ptr, i64 } %callN) + br label %if.merge.247 + +if.merge.247: ; preds = %if.else.246, %if.then.245 + %bp = phi { ptr, i64 } [ { ptr @str.271, i64 4 }, %if.then.245 ], [ %callN, %if.else.246 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_ArenaChunk(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.248, label %if.else.249 + +if.then.248: ; preds = %entry + br label %if.merge.250 + +if.else.249: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.276, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.275, i64 11 }, { ptr, i64 } %callN) + br label %if.merge.250 + +if.merge.250: ; preds = %if.else.249, %if.then.248 + %bp = phi { ptr, i64 } [ { ptr @str.274, i64 4 }, %if.then.248 ], [ %callN, %if.else.249 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_Arena(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.251, label %if.else.252 + +if.then.251: ; preds = %entry + br label %if.merge.253 + +if.else.252: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.279, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.278, i64 6 }, { ptr, i64 } %callN) + br label %if.merge.253 + +if.merge.253: ; preds = %if.else.252, %if.then.251 + %bp = phi { ptr, i64 } [ { ptr @str.277, i64 4 }, %if.then.251 ], [ %callN, %if.else.252 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__mptr_u8(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + store ptr %load, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.254, label %if.else.255 + +if.then.254: ; preds = %entry + br label %if.merge.256 + +if.else.255: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.282, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.281, i64 5 }, { ptr, i64 } %callN) + br label %if.merge.256 + +if.merge.256: ; preds = %if.else.255, %if.then.254 + %bp = phi { ptr, i64 } [ { ptr @str.280, i64 4 }, %if.then.254 ], [ %callN, %if.else.255 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_BufAlloc(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.257, label %if.else.258 + +if.then.257: ; preds = %entry + br label %if.merge.259 + +if.else.258: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.285, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.284, i64 9 }, { ptr, i64 } %callN) + br label %if.merge.259 + +if.merge.259: ; preds = %if.else.258, %if.then.257 + %bp = phi { ptr, i64 } [ { ptr @str.283, i64 4 }, %if.then.257 ], [ %callN, %if.else.258 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_TrackingAllocator(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.260, label %if.else.261 + +if.then.260: ; preds = %entry + br label %if.merge.262 + +if.else.261: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.288, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.287, i64 18 }, { ptr, i64 } %callN) + br label %if.merge.262 + +if.merge.262: ; preds = %if.else.261, %if.then.260 + %bp = phi { ptr, i64 } [ { ptr @str.286, i64 4 }, %if.then.260 ], [ %callN, %if.else.261 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_NSString(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.263, label %if.else.264 + +if.then.263: ; preds = %entry + br label %if.merge.265 + +if.else.264: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.291, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.290, i64 9 }, { ptr, i64 } %callN) + br label %if.merge.265 + +if.merge.265: ; preds = %if.else.264, %if.then.263 + %bp = phi { ptr, i64 } [ { ptr @str.289, i64 4 }, %if.then.263 ], [ %callN, %if.else.264 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_ptr_CAllocator(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.266, label %if.else.267 + +if.then.266: ; preds = %entry + br label %if.merge.268 + +if.else.267: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.294, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.293, i64 12 }, { ptr, i64 } %callN) + br label %if.merge.268 + +if.merge.268: ; preds = %if.else.267, %if.then.266 + %bp = phi { ptr, i64 } [ { ptr @str.292, i64 4 }, %if.then.266 ], [ %callN, %if.else.267 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_s64(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.269, label %if.else.270 + +if.then.269: ; preds = %entry + br label %if.merge.271 + +if.else.270: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.297, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.296, i64 4 }, { ptr, i64 } %callN) + br label %if.merge.271 + +if.merge.271: ; preds = %if.else.270, %if.then.269 + %bp = phi { ptr, i64 } [ { ptr @str.295, i64 4 }, %if.then.269 ], [ %callN, %if.else.270 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_ptr_void(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.272, label %if.else.273 + +if.then.272: ; preds = %entry + br label %if.merge.274 + +if.else.273: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.300, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.299, i64 6 }, { ptr, i64 } %callN) + br label %if.merge.274 + +if.merge.274: ; preds = %if.else.273, %if.then.272 + %bp = phi { ptr, i64 } [ { ptr @str.298, i64 4 }, %if.then.272 ], [ %callN, %if.else.273 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_SxBox(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.275, label %if.else.276 + +if.then.275: ; preds = %entry + br label %if.merge.277 + +if.else.276: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.303, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.302, i64 6 }, { ptr, i64 } %callN) + br label %if.merge.277 + +if.merge.277: ; preds = %if.else.276, %if.then.275 + %bp = phi { ptr, i64 } [ { ptr @str.301, i64 4 }, %if.then.275 ], [ %callN, %if.else.276 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_ptr_SxBox(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.278, label %if.else.279 + +if.then.278: ; preds = %entry + br label %if.merge.280 + +if.else.279: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.306, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.305, i64 7 }, { ptr, i64 } %callN) + br label %if.merge.280 + +if.merge.280: ; preds = %if.else.279, %if.then.278 + %bp = phi { ptr, i64 } [ { ptr @str.304, i64 4 }, %if.then.278 ], [ %callN, %if.else.279 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_s32(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.281, label %if.else.282 + +if.then.281: ; preds = %entry + br label %if.merge.283 + +if.else.282: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.309, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.308, i64 4 }, { ptr, i64 } %callN) + br label %if.merge.283 + +if.merge.283: ; preds = %if.else.282, %if.then.281 + %bp = phi { ptr, i64 } [ { ptr @str.307, i64 4 }, %if.then.281 ], [ %callN, %if.else.282 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_string(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.284, label %if.else.285 + +if.then.284: ; preds = %entry + br label %if.merge.286 + +if.else.285: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.312, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.311, i64 7 }, { ptr, i64 } %callN) + br label %if.merge.286 + +if.merge.286: ; preds = %if.else.285, %if.then.284 + %bp = phi { ptr, i64 } [ { ptr @str.310, i64 4 }, %if.then.284 ], [ %callN, %if.else.285 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_Any(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.287, label %if.else.288 + +if.then.287: ; preds = %entry + br label %if.merge.289 + +if.else.288: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.315, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.314, i64 4 }, { ptr, i64 } %callN) + br label %if.merge.289 + +if.merge.289: ; preds = %if.else.288, %if.then.287 + %bp = phi { ptr, i64 } [ { ptr @str.313, i64 4 }, %if.then.287 ], [ %callN, %if.else.288 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_SL_Any(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.290, label %if.else.291 + +if.then.290: ; preds = %entry + br label %if.merge.292 + +if.else.291: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.318, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.317, i64 6 }, { ptr, i64 } %callN) + br label %if.merge.292 + +if.merge.292: ; preds = %if.else.291, %if.then.290 + %bp = phi { ptr, i64 } [ { ptr @str.316, i64 4 }, %if.then.290 ], [ %callN, %if.else.291 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_u8(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.293, label %if.else.294 + +if.then.293: ; preds = %entry + br label %if.merge.295 + +if.else.294: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.321, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.320, i64 3 }, { ptr, i64 } %callN) + br label %if.merge.295 + +if.merge.295: ; preds = %if.else.294, %if.then.293 + %bp = phi { ptr, i64 } [ { ptr @str.319, i64 4 }, %if.then.293 ], [ %callN, %if.else.294 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_bool(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.296, label %if.else.297 + +if.then.296: ; preds = %entry + br label %if.merge.298 + +if.else.297: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.324, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.323, i64 5 }, { ptr, i64 } %callN) + br label %if.merge.298 + +if.merge.298: ; preds = %if.else.297, %if.then.296 + %bp = phi { ptr, i64 } [ { ptr @str.322, i64 4 }, %if.then.296 ], [ %callN, %if.else.297 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_AR_1_Any(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.299, label %if.else.300 + +if.then.299: ; preds = %entry + br label %if.merge.301 + +if.else.300: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.327, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.326, i64 7 }, { ptr, i64 } %callN) + br label %if.merge.301 + +if.merge.301: ; preds = %if.else.300, %if.then.299 + %bp = phi { ptr, i64 } [ { ptr @str.325, i64 4 }, %if.then.299 ], [ %callN, %if.else.300 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define internal { ptr, i64 } @pointer_to_string__ptr_AR_3_Any(ptr %0, ptr %1) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + %load = load ptr, ptr %alloca, align 8 + %pti = ptrtoint ptr %load to i64 + store i64 %pti, ptr %allocaN, align 8 + %loadN = load i64, ptr %allocaN, align 8 + %icmp = icmp eq i64 %loadN, 0 + br i1 %icmp, label %if.then.302, label %if.else.303 + +if.then.302: ; preds = %entry + br label %if.merge.304 + +if.else.303: ; preds = %entry + %loadN = load i64, ptr %allocaN, align 8 + %call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.330, i64 3 }, { ptr, i64 } %call) + %callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.329, i64 7 }, { ptr, i64 } %callN) + br label %if.merge.304 + +if.merge.304: ; preds = %if.else.303, %if.then.302 + %bp = phi { ptr, i64 } [ { ptr @str.328, i64 4 }, %if.then.302 ], [ %callN, %if.else.303 ] + ret { ptr, i64 } %bp +} + +; Function Attrs: nounwind +define ptr @__SxBox_alloc_imp(ptr %0, ptr %1) #0 { +entry: + %call = call ptr @class_createInstance(ptr %0, i64 0) + %load = load { { ptr, ptr, ptr }, ptr }, ptr @__sx_default_context, align 8 + %sg = extractvalue { { ptr, ptr, ptr }, ptr } %load, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 0 + %sgN = extractvalue { ptr, ptr, ptr } %sg, 1 + %icall = call ptr %sgN(ptr @__sx_default_context, ptr %sgN, i64 40) + %callN = call ptr @memset(ptr %icall, i32 0, i64 40) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %icall, i32 0, i32 0 + store { ptr, ptr, ptr } %sg, ptr %gep, align 8 + %loadN = load ptr, ptr @__SxBox_state_ivar, align 8 + call void @object_setIvar(ptr %call, ptr %loadN, ptr %icall) + ret ptr %call +} + +; Function Attrs: nounwind +define void @__SxBox_dealloc_imp(ptr %0, ptr %1) #0 { +entry: + %load = load ptr, ptr @__SxBox_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %0, ptr %load) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %call, i32 0, i32 0 + %loadN = load { ptr, ptr, ptr }, ptr %gep, align 8 + %sg = extractvalue { ptr, ptr, ptr } %loadN, 0 + %sgN = extractvalue { ptr, ptr, ptr } %loadN, 2 + call void %sgN(ptr @__sx_default_context, ptr %sg, ptr %call) + call void @object_setIvar(ptr %0, ptr %load, ptr null) + %alloca = alloca { ptr, ptr }, align 8 + %gepN = getelementptr inbounds { ptr, ptr }, ptr %alloca, i32 0, i32 0 + store ptr %0, ptr %gepN, align 8 + %loadN = load ptr, ptr @__SxBox_class, align 8 + %gepN = getelementptr inbounds { ptr, ptr }, ptr %alloca, i32 0, i32 1 + store ptr %loadN, ptr %gepN, align 8 + %callN = call ptr @sel_registerName(ptr @__sx_objc_cstr_dealloc) + call void @objc_msgSendSuper2(ptr %alloca, ptr %callN) + ret void +} + +; Function Attrs: nounwind +declare void @objc_msgSendSuper2(ptr, ptr) #0 + +; Function Attrs: nounwind +define i32 @__SxBox_width_imp(ptr %0, ptr %1) #0 { +entry: + %load = load ptr, ptr @__SxBox_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %0, ptr %load) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %call, i32 0, i32 1 + %loadN = load i32, ptr %gep, align 4 + ret i32 %loadN +} + +; Function Attrs: nounwind +define void @__SxBox_setWidth_imp(ptr %0, ptr %1, i32 %2) #0 { +entry: + %load = load ptr, ptr @__SxBox_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %0, ptr %load) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %call, i32 0, i32 1 + store i32 %2, ptr %gep, align 4 + ret void +} + +; Function Attrs: nounwind +define i32 @__SxBox_height_imp(ptr %0, ptr %1) #0 { +entry: + %load = load ptr, ptr @__SxBox_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %0, ptr %load) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %call, i32 0, i32 2 + %loadN = load i32, ptr %gep, align 4 + ret i32 %loadN +} + +; Function Attrs: nounwind +define void @__SxBox_setHeight_imp(ptr %0, ptr %1, i32 %2) #0 { +entry: + %load = load ptr, ptr @__SxBox_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %0, ptr %load) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %call, i32 0, i32 2 + store i32 %2, ptr %gep, align 4 + ret void +} + +; Function Attrs: nounwind +define i32 @__SxBox_area_imp(ptr %0, ptr %1) #0 { +entry: + %load = load ptr, ptr @__SxBox_state_ivar, align 8 + %call = call ptr @object_getIvar(ptr %0, ptr %load) + %gep = getelementptr inbounds { { ptr, ptr, ptr }, i32, i32, i32 }, ptr %call, i32 0, i32 3 + %loadN = load i32, ptr %gep, align 4 + ret i32 %loadN +} + +declare i64 @write(i32, ptr, i64) + +define internal void @__sx_objc_selector_init() { +entry: + %sel = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_) + store ptr %sel, ptr @OBJC_SELECTOR_REFERENCES_init, align 8 + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.331) + store ptr %selN, ptr @OBJC_SELECTOR_REFERENCES_setWidth_, align 8 + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.332) + store ptr %selN, ptr @OBJC_SELECTOR_REFERENCES_setHeight_, align 8 + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.333) + store ptr %selN, ptr @OBJC_SELECTOR_REFERENCES_width, align 8 + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.334) + store ptr %selN, ptr @OBJC_SELECTOR_REFERENCES_height, align 8 + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.335) + store ptr %selN, ptr @OBJC_SELECTOR_REFERENCES_area, align 8 + ret void +} + +declare i8 @class_addIvar(ptr, ptr, i64, i8, ptr) + +declare ptr @class_getInstanceVariable(ptr, ptr) + +define internal void @__sx_objc_defined_class_init() { +entry: + %super_cls = call ptr @objc_getClass(ptr @OBJC_CLASS_NAME_) + %cls = call ptr @objc_allocateClassPair(ptr %super_cls, ptr @OBJC_CLASS_NAME_.336, i64 0) + %0 = call i8 @class_addIvar(ptr %cls, ptr @OBJC_IVAR_NAME_, i64 8, i8 3, ptr @OBJC_IVAR_TYPE_) + %metacls = call ptr @object_getClass(ptr %cls) + %sel = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.337) + %1 = call i1 @class_addMethod(ptr %cls, ptr %sel, ptr @__SxBox_width_imp, ptr @OBJC_METH_VAR_TYPE_) + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.338) + %2 = call i1 @class_addMethod(ptr %cls, ptr %selN, ptr @__SxBox_setWidth_imp, ptr @OBJC_METH_VAR_TYPE_.339) + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.340) + %3 = call i1 @class_addMethod(ptr %cls, ptr %selN, ptr @__SxBox_height_imp, ptr @OBJC_METH_VAR_TYPE_.341) + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.342) + %4 = call i1 @class_addMethod(ptr %cls, ptr %selN, ptr @__SxBox_setHeight_imp, ptr @OBJC_METH_VAR_TYPE_.343) + %selN = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.344) + %5 = call i1 @class_addMethod(ptr %cls, ptr %selN, ptr @__SxBox_area_imp, ptr @OBJC_METH_VAR_TYPE_.345) + call void @objc_registerClassPair(ptr %cls) + store ptr %cls, ptr @__SxBox_class, align 8 + %sel_dealloc = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.346) + %6 = call i1 @class_addMethod(ptr %cls, ptr %sel_dealloc, ptr @__SxBox_dealloc_imp, ptr @OBJC_METH_VAR_TYPE_.347) + %sel_alloc = call ptr @sel_registerName(ptr @OBJC_METH_VAR_NAME_.348) + %7 = call i1 @class_addMethod(ptr %metacls, ptr %sel_alloc, ptr @__SxBox_alloc_imp, ptr @OBJC_METH_VAR_TYPE_.349) + %iv = call ptr @class_getInstanceVariable(ptr %cls, ptr @OBJC_IVAR_NAME_) + store ptr %iv, ptr @__SxBox_state_ivar, align 8 + ret void +} + +define internal void @__sx_objc_class_init() { +entry: + %cls = call ptr @objc_getClass(ptr @OBJC_CLASS_NAME_.350) + store ptr %cls, ptr @OBJC_CLASSLIST_REFERENCES_SxBox, align 8 + ret void +} From 836583d7f7f0fe3fe4a6de1d9f05fb10f240c901 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 10:26:45 +0300 Subject: [PATCH 07/26] test(backend): pin JNI constructor (FindClass//NewObject) before A7.3 extraction (A7.3 scaffolding coverage fix) Codex review of 91651e3 noted no .ir snapshot pinned emitJniConstructor's distinct FindClass -> GetMethodID("") -> NewObject shape; 1402/1418/1408 cover regular/ static GetMethodID slot caching, not constructor emission. Add examples/expected/1425-ffi-jni-main-03-ctor.ir (FindClass x4 / GetMethodID x4 / NewObject x2 / ), path-free + idempotent, trailing newline trimmed. Suite count unchanged (snapshot on an existing example). Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (git diff --check clean; only the intended ctor snapshot added). --- .../expected/1425-ffi-jni-main-03-ctor.ir | 313 ++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 examples/expected/1425-ffi-jni-main-03-ctor.ir diff --git a/examples/expected/1425-ffi-jni-main-03-ctor.ir b/examples/expected/1425-ffi-jni-main-03-ctor.ir new file mode 100644 index 0000000..9068155 --- /dev/null +++ b/examples/expected/1425-ffi-jni-main-03-ctor.ir @@ -0,0 +1,313 @@ + +@OS = internal global i64 0 +@ARCH = internal global i64 0 +@POINTER_SIZE = internal global i64 8 +@g_held_view = internal global ptr null +@__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"onCreate\00", align 1 +@str.1 = private unnamed_addr constant [23 x i8] c"(Landroid/os/Bundle;)V\00", align 1 +@jni.parent.path = private unnamed_addr constant [21 x i8] c"android/app/Activity\00", align 1 +@str.2 = private unnamed_addr constant [7 x i8] c"\00", align 1 +@str.3 = private unnamed_addr constant [29 x i8] c"(Landroid/content/Context;)V\00", align 1 +@jni.ctor.path = private unnamed_addr constant [25 x i8] c"android/view/SurfaceView\00", align 1 + +; Function Attrs: nounwind +declare void @out(ptr) #0 + +declare ptr @malloc(i64) + +declare void @free(ptr) + +declare ptr @memcpy(ptr, ptr, i64) + +declare ptr @memset(ptr, i32, i64) + +; Function Attrs: nounwind +define internal ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca i64, align 8 + store i64 %2, ptr %allocaN, align 8 + %load = load i64, ptr %allocaN, align 8 + %call = call ptr @malloc(i64 %load) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + %alloca = alloca ptr, align 8 + store ptr %1, ptr %alloca, align 8 + %allocaN = alloca ptr, align 8 + store ptr %2, ptr %allocaN, align 8 + %load = load ptr, ptr %allocaN, align 8 + call void @free(ptr %load) + ret void +} + +; Function Attrs: nounwind +declare i64 @GPA.init(ptr) #0 + +; Function Attrs: nounwind +declare ptr @GPA.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @GPA.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.add_chunk(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.init(ptr sret({ ptr, i64, { ptr, ptr, ptr } }), ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @Arena.deinit(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @Arena.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @Arena.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.init(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.reset(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BufAlloc.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @BufAlloc.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.init(ptr sret({ { ptr, ptr, ptr }, i64, i64, i64 }), ptr, ptr) #0 + +; Function Attrs: nounwind +declare i64 @TrackingAllocator.leak_count(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.report(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @TrackingAllocator.alloc(ptr, ptr, i64) #0 + +; Function Attrs: nounwind +declare void @TrackingAllocator.dealloc(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @cstring(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @int_to_string(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @bool_to_string(ptr, i1) #0 + +; Function Attrs: nounwind +declare ptr @float_to_string(ptr, double) #0 + +; Function Attrs: nounwind +declare void @hex_group(ptr, ptr, i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @int_to_hex_string(ptr, i64) #0 + +; Function Attrs: nounwind +declare ptr @concat(ptr, ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @substr(ptr, ptr, i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @xml_escape(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @path_join(ptr, ptr) #0 + +; Function Attrs: nounwind +declare ptr @any_to_string(ptr, [2 x i64]) #0 + +; Function Attrs: nounwind +declare ptr @build_format(ptr, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.add_link_flag(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.add_framework(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_output_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_wasm_shell(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.add_asset_dir(i64, ptr, ptr) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.asset_dir_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.asset_dir_src_at(i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.asset_dir_dest_at(i64, i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_post_link_callback(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_post_link_module(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.binary_path(i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_bundle_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_bundle_id(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_codesign_identity(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_provisioning_profile(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.bundle_path(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.bundle_id(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.codesign_identity(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.provisioning_profile(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.target_triple(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_macos(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios_device(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_ios_simulator(i64) #0 + +; Function Attrs: nounwind +declare i1 @BuildOptions.is_android(i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.framework_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.framework_at(i64, i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.framework_path_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.framework_path_at(i64, i64) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_manifest_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare void @BuildOptions.set_keystore_path(i64, ptr) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.manifest_path(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.keystore_path(i64) #0 + +; Function Attrs: nounwind +declare i64 @BuildOptions.jni_main_count(i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.jni_main_foreign_path_at(i64, i64) #0 + +; Function Attrs: nounwind +declare ptr @BuildOptions.jni_main_java_source_at(i64, i64) #0 + +; Function Attrs: nounwind +declare i64 @build_options() #0 + +; Function Attrs: nounwind +define i32 @main() #0 { +entry: + ret i32 0 +} + +; Function Attrs: nounwind +define internal ptr @__thunk_CAllocator_Allocator_alloc(ptr %0, ptr %1, i64 %2) #0 { +entry: + %call = call ptr @CAllocator.alloc(ptr %0, ptr %1, i64 %2) + ret ptr %call +} + +; Function Attrs: nounwind +define internal void @__thunk_CAllocator_Allocator_dealloc(ptr %0, ptr %1, ptr %2) #0 { +entry: + call void @CAllocator.dealloc(ptr %0, ptr %1, ptr %2) + ret void +} + +; Function Attrs: nounwind +define void @Java_co_swipelab_sxjnictor_SxApp_sx_1onCreate(ptr %0, ptr %1, ptr %2) #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 + %allocaN = alloca ptr, align 8 + store ptr %2, ptr %allocaN, align 8 + %load = load ptr, ptr %alloca, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %jni.ifs = load ptr, ptr %load, align 8 + %3 = getelementptr inbounds ptr, ptr %jni.ifs, i32 6 + %jni.FindClass = load ptr, ptr %3, align 8 + %jni.parent.cls = call ptr %jni.FindClass(ptr %load, ptr @jni.parent.path) + %4 = getelementptr inbounds ptr, ptr %jni.ifs, i32 33 + %jni.GetMethodID = load ptr, ptr %4, align 8 + %jni.mid = call ptr %jni.GetMethodID(ptr %load, ptr %jni.parent.cls, ptr @str, ptr @str.1) + %jni.parent.cls.slot = alloca ptr, align 8 + store ptr %jni.parent.cls, ptr %jni.parent.cls.slot, align 8 + %5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 91 + %jni.callfn.nonvirtual = load ptr, ptr %5, align 8 + call void %jni.callfn.nonvirtual(ptr %load, ptr %loadN, ptr %jni.parent.cls, ptr %jni.mid, ptr %loadN) + %allocaN = alloca ptr, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + store ptr %loadN, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + %jni.ifs8 = load ptr, ptr %load, align 8 + %6 = getelementptr inbounds ptr, ptr %jni.ifs8, i32 6 + %jni.FindClass9 = load ptr, ptr %6, align 8 + %jni.ctor.cls = call ptr %jni.FindClass9(ptr %load, ptr @jni.ctor.path) + %7 = getelementptr inbounds ptr, ptr %jni.ifs8, i32 33 + %jni.GetMethodID10 = load ptr, ptr %7, align 8 + %jni.ctor.mid = call ptr %jni.GetMethodID10(ptr %load, ptr %jni.ctor.cls, ptr @str.2, ptr @str.3) + %8 = getelementptr inbounds ptr, ptr %jni.ifs8, i32 28 + %jni.NewObject = load ptr, ptr %8, align 8 + %jni.new.obj = call ptr %jni.NewObject(ptr %load, ptr %jni.ctor.cls, ptr %jni.ctor.mid, ptr %loadN) + %allocaN = alloca ptr, align 8 + store ptr %jni.new.obj, ptr %allocaN, align 8 + %loadN = load ptr, ptr %allocaN, align 8 + store ptr %loadN, ptr @g_held_view, align 8 + ret void +} From e8c33bfc003c2b2a86b8cf4f6b52ebb5a6f4bf12 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 10:35:15 +0300 Subject: [PATCH 08/26] refactor(backend): extract Obj-C runtime constructors into src/backend/llvm/ffi_ctors.zig (A7.3 slice 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the Obj-C module-init constructor emission out of emit_llvm.zig into a FfiCtors backend *LLVMEmitter facade (field `e`). Behavior-preserving relocation — self.* -> self.e.* only. - src/backend/llvm/ffi_ctors.zig (FfiCtors): emitObjcSelectorInit (cached SEL init), emitObjcClassInit (objc_getClass class-object cache), and emitObjcDefinedClassInit (class-pair registration: ivars, method IMP table, +alloc/-dealloc IMPs, #implements protocol conformances). Emit-time caches (ir_mod.objc_*_cache) + global_map + cached LLVM handles read via self.e.*. - 3 call sites in LLVMEmitter.emit routed via a new ffiCtors() accessor. - Shared infra stays in emit_llvm.zig, widened to pub (the facade calls back): lazyDeclareCRuntime (11 callers), emitPrivateCString (11 callers), injectCtorIntoMain (the moved defined-class ctor's callee). No @llvm.global_ctors shape / IMP-table / ivar / protocol-conformance change. Pins: 1309 (class-method lowering), 1319 (property getter/setter IMPs), 1314 (alloc/dealloc IMPs), 1332 (sret + addMethod) all green. Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (no churn). --- src/backend/llvm/ffi_ctors.zig | 476 ++++++++++++++++++++++++++++++++ src/ir/emit_llvm.zig | 491 +-------------------------------- 2 files changed, 487 insertions(+), 480 deletions(-) create mode 100644 src/backend/llvm/ffi_ctors.zig diff --git a/src/backend/llvm/ffi_ctors.zig b/src/backend/llvm/ffi_ctors.zig new file mode 100644 index 0000000..5bfee0d --- /dev/null +++ b/src/backend/llvm/ffi_ctors.zig @@ -0,0 +1,476 @@ +const std = @import("std"); +const llvm = @import("../../llvm_api.zig"); +const c = llvm.c; +const emit = @import("../../ir/emit_llvm.zig"); + +const LLVMEmitter = emit.LLVMEmitter; + +/// Obj-C / JNI runtime-constructor emission (architecture phase A7.3), extracted +/// from `LLVMEmitter`. A backend `*LLVMEmitter` facade (field `e`): it builds the +/// module-init constructors that populate the cached selector / class slots and +/// register sx-defined `#objc_class` class pairs (IMP tables, ivars, +alloc / +/// -dealloc / property IMPs, `#implements` protocol conformances). Reads the +/// emit-time caches (`ir_mod.objc_*_cache`, `global_map`) + cached LLVM handles +/// via `self.e.*`; the shared infra it calls back into +/// (`lazyDeclareCRuntime`/`emitPrivateCString`/`injectCtorIntoMain`) stays on +/// `LLVMEmitter`. `LLVMEmitter.emit` drives pass order via `self.ffiCtors()`. +pub const FfiCtors = struct { + e: *LLVMEmitter, + + pub fn emitObjcSelectorInit(self: FfiCtors) void { + if (self.e.ir_mod.objc_selector_cache.items.len == 0) return; + + // Lazy-declare sel_registerName for the constructor body — + // lower.zig only declares it when a non-literal selector + // appears, which the constructor doesn't depend on. + const sel_reg_name = "sel_registerName"; + const sel_reg_z = self.e.alloc.dupeZ(u8, sel_reg_name) catch unreachable; + defer self.e.alloc.free(sel_reg_z); + var sel_reg_fn = c.LLVMGetNamedFunction(self.e.llvm_module, sel_reg_z.ptr); + var sel_reg_ty: c.LLVMTypeRef = undefined; + if (sel_reg_fn == null) { + var params: [1]c.LLVMTypeRef = .{self.e.cached_ptr}; + sel_reg_ty = c.LLVMFunctionType(self.e.cached_ptr, ¶ms, 1, 0); + sel_reg_fn = c.LLVMAddFunction(self.e.llvm_module, sel_reg_z.ptr, sel_reg_ty); + c.LLVMSetLinkage(sel_reg_fn, c.LLVMExternalLinkage); + } else { + sel_reg_ty = c.LLVMGlobalGetValueType(sel_reg_fn); + } + + // Constructor: void __sx_objc_selector_init(). + var no_params: [0]c.LLVMTypeRef = .{}; + const ctor_ty = c.LLVMFunctionType(self.e.cached_void, &no_params, 0, 0); + const ctor = c.LLVMAddFunction(self.e.llvm_module, "__sx_objc_selector_init", ctor_ty); + c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage); + const entry = c.LLVMAppendBasicBlockInContext(self.e.context, ctor, "entry"); + c.LLVMPositionBuilderAtEnd(self.e.builder, entry); + + for (self.e.ir_mod.objc_selector_cache.items) |entry_kv| { + const sel_str = entry_kv.sel; + const slot_gid = entry_kv.slot; + const slot_global = self.e.global_map.get(@intCast(slot_gid.index())) orelse continue; + + // Method-name C-string — names match clang's convention + // so debuggers / nm / dyld see the same symbols, even + // though the surrounding section tagging isn't load- + // bearing in our JIT path. + const meth_str_z = self.e.alloc.allocSentinel(u8, sel_str.len, 0) catch continue; + defer self.e.alloc.free(meth_str_z); + @memcpy(meth_str_z[0..sel_str.len], sel_str); + const str_const = c.LLVMConstStringInContext(self.e.context, meth_str_z.ptr, @intCast(sel_str.len), 0); + const str_global = c.LLVMAddGlobal(self.e.llvm_module, c.LLVMTypeOf(str_const), "OBJC_METH_VAR_NAME_"); + c.LLVMSetInitializer(str_global, str_const); + c.LLVMSetLinkage(str_global, c.LLVMPrivateLinkage); + c.LLVMSetGlobalConstant(str_global, 1); + c.LLVMSetUnnamedAddress(str_global, c.LLVMGlobalUnnamedAddr); + + var sel_args: [1]c.LLVMValueRef = .{str_global}; + const sel_val = c.LLVMBuildCall2(self.e.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel"); + _ = c.LLVMBuildStore(self.e.builder, sel_val, slot_global); + } + _ = c.LLVMBuildRetVoid(self.e.builder); + + // Register the constructor in @llvm.global_ctors. dyld picks + // this up for a fully-linked binary at load time. + const i32_ty = self.e.cached_i32; + const ptr_ty = self.e.cached_ptr; + var ctor_field_types: [3]c.LLVMTypeRef = .{ i32_ty, ptr_ty, ptr_ty }; + const ctor_struct_ty = c.LLVMStructTypeInContext(self.e.context, &ctor_field_types, 3, 0); + var ctor_fields: [3]c.LLVMValueRef = .{ + c.LLVMConstInt(i32_ty, 65535, 0), + ctor, + c.LLVMConstNull(ptr_ty), + }; + const ctor_entry = c.LLVMConstNamedStruct(ctor_struct_ty, &ctor_fields, 3); + const ctors_arr_ty = c.LLVMArrayType2(ctor_struct_ty, 1); + var ctor_entries: [1]c.LLVMValueRef = .{ctor_entry}; + const ctors_init = c.LLVMConstArray2(ctor_struct_ty, &ctor_entries, 1); + const ctors_global = c.LLVMAddGlobal(self.e.llvm_module, ctors_arr_ty, "llvm.global_ctors"); + c.LLVMSetInitializer(ctors_global, ctors_init); + c.LLVMSetLinkage(ctors_global, c.LLVMAppendingLinkage); + + // BUT — LLVM's ORC JIT (the engine for `sx run`) doesn't + // automatically run `@llvm.global_ctors`. Inject a direct + // call from `main`'s entry block as well; idempotent under + // dyld (sel_registerName returns the same SEL on second call). + const main_z = "main"; + const main_fn = c.LLVMGetNamedFunction(self.e.llvm_module, main_z); + if (main_fn != null) { + const entry_bb = c.LLVMGetEntryBasicBlock(main_fn); + const first_inst = c.LLVMGetFirstInstruction(entry_bb); + if (first_inst != null) { + c.LLVMPositionBuilderBefore(self.e.builder, first_inst); + } else { + c.LLVMPositionBuilderAtEnd(self.e.builder, entry_bb); + } + var no_args: [0]c.LLVMValueRef = .{}; + _ = c.LLVMBuildCall2(self.e.builder, ctor_ty, ctor, &no_args, 0, ""); + } + } + + /// Phase 3.1 companion to `emitObjcSelectorInit`. Walks + /// `module.objc_class_cache` and synthesizes a constructor that + /// populates each cached `Class*` slot via `objc_getClass(name)` + /// exactly once at module-init. Registered in `@llvm.global_ctors` + /// AND injected at the top of `main()` for the ORC JIT path. + pub fn emitObjcClassInit(self: FfiCtors) void { + if (self.e.ir_mod.objc_class_cache.items.len == 0) return; + + // Lazy-declare objc_getClass(name: *u8) -> *void. + const get_class_name = "objc_getClass"; + const get_class_z = self.e.alloc.dupeZ(u8, get_class_name) catch unreachable; + defer self.e.alloc.free(get_class_z); + var get_class_fn = c.LLVMGetNamedFunction(self.e.llvm_module, get_class_z.ptr); + var get_class_ty: c.LLVMTypeRef = undefined; + if (get_class_fn == null) { + var params: [1]c.LLVMTypeRef = .{self.e.cached_ptr}; + get_class_ty = c.LLVMFunctionType(self.e.cached_ptr, ¶ms, 1, 0); + get_class_fn = c.LLVMAddFunction(self.e.llvm_module, get_class_z.ptr, get_class_ty); + c.LLVMSetLinkage(get_class_fn, c.LLVMExternalLinkage); + } else { + get_class_ty = c.LLVMGlobalGetValueType(get_class_fn); + } + + // Constructor: void __sx_objc_class_init(). + var no_params: [0]c.LLVMTypeRef = .{}; + const ctor_ty = c.LLVMFunctionType(self.e.cached_void, &no_params, 0, 0); + const ctor = c.LLVMAddFunction(self.e.llvm_module, "__sx_objc_class_init", ctor_ty); + c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage); + const entry = c.LLVMAppendBasicBlockInContext(self.e.context, ctor, "entry"); + c.LLVMPositionBuilderAtEnd(self.e.builder, entry); + + for (self.e.ir_mod.objc_class_cache.items) |entry_kv| { + const class_name = entry_kv.name; + const slot_gid = entry_kv.slot; + const slot_global = self.e.global_map.get(@intCast(slot_gid.index())) orelse continue; + + // Class-name C-string. + const name_z = self.e.alloc.allocSentinel(u8, class_name.len, 0) catch continue; + defer self.e.alloc.free(name_z); + @memcpy(name_z[0..class_name.len], class_name); + const str_const = c.LLVMConstStringInContext(self.e.context, name_z.ptr, @intCast(class_name.len), 0); + const str_global = c.LLVMAddGlobal(self.e.llvm_module, c.LLVMTypeOf(str_const), "OBJC_CLASS_NAME_"); + c.LLVMSetInitializer(str_global, str_const); + c.LLVMSetLinkage(str_global, c.LLVMPrivateLinkage); + c.LLVMSetGlobalConstant(str_global, 1); + c.LLVMSetUnnamedAddress(str_global, c.LLVMGlobalUnnamedAddr); + + var call_args: [1]c.LLVMValueRef = .{str_global}; + const class_val = c.LLVMBuildCall2(self.e.builder, get_class_ty, get_class_fn, &call_args, 1, "cls"); + _ = c.LLVMBuildStore(self.e.builder, class_val, slot_global); + } + _ = c.LLVMBuildRetVoid(self.e.builder); + + // Register in @llvm.global_ctors for AOT + inject into main for ORC JIT. + const i32_ty = self.e.cached_i32; + const ptr_ty = self.e.cached_ptr; + var ctor_field_types: [3]c.LLVMTypeRef = .{ i32_ty, ptr_ty, ptr_ty }; + const ctor_struct_ty = c.LLVMStructTypeInContext(self.e.context, &ctor_field_types, 3, 0); + var ctor_fields: [3]c.LLVMValueRef = .{ + c.LLVMConstInt(i32_ty, 65535, 0), + ctor, + c.LLVMConstNull(ptr_ty), + }; + const ctor_entry = c.LLVMConstNamedStruct(ctor_struct_ty, &ctor_fields, 3); + + // Append-vs-replace the existing global_ctors. Selector init may + // have created `@llvm.global_ctors` already — extend its array + // rather than overwriting. + const existing_z = "llvm.global_ctors"; + const existing = c.LLVMGetNamedGlobal(self.e.llvm_module, existing_z); + if (existing != null) { + const existing_init = c.LLVMGetInitializer(existing); + const existing_arr_ty = c.LLVMGlobalGetValueType(existing); + const old_count = c.LLVMGetArrayLength(existing_arr_ty); + const new_count: c_uint = old_count + 1; + var new_entries = std.ArrayList(c.LLVMValueRef).empty; + defer new_entries.deinit(self.e.alloc); + var i: c_uint = 0; + while (i < old_count) : (i += 1) { + new_entries.append(self.e.alloc, c.LLVMGetAggregateElement(existing_init, i)) catch unreachable; + } + new_entries.append(self.e.alloc, ctor_entry) catch unreachable; + const new_arr_ty = c.LLVMArrayType2(ctor_struct_ty, new_count); + const new_init = c.LLVMConstArray2(ctor_struct_ty, new_entries.items.ptr, new_count); + const new_global = c.LLVMAddGlobal(self.e.llvm_module, new_arr_ty, "llvm.global_ctors.new"); + c.LLVMSetInitializer(new_global, new_init); + c.LLVMSetLinkage(new_global, c.LLVMAppendingLinkage); + c.LLVMSetValueName2(existing, "llvm.global_ctors.old", "llvm.global_ctors.old".len); + c.LLVMSetValueName2(new_global, "llvm.global_ctors", "llvm.global_ctors".len); + c.LLVMDeleteGlobal(existing); + } else { + const ctors_arr_ty = c.LLVMArrayType2(ctor_struct_ty, 1); + var ctor_entries: [1]c.LLVMValueRef = .{ctor_entry}; + const ctors_init = c.LLVMConstArray2(ctor_struct_ty, &ctor_entries, 1); + const ctors_global = c.LLVMAddGlobal(self.e.llvm_module, ctors_arr_ty, "llvm.global_ctors"); + c.LLVMSetInitializer(ctors_global, ctors_init); + c.LLVMSetLinkage(ctors_global, c.LLVMAppendingLinkage); + } + + // ORC JIT injection: same trick as emitObjcSelectorInit. Inject a + // direct call from main's entry so the JIT path populates the + // slots too. Must run AFTER the selector init's main injection + // (selectors are needed independently of class objects), so we + // place this call AFTER the first instruction (which is the + // selector-init call, if present) rather than at the very top. + const main_z = "main"; + const main_fn = c.LLVMGetNamedFunction(self.e.llvm_module, main_z); + if (main_fn != null) { + const entry_bb = c.LLVMGetEntryBasicBlock(main_fn); + // Walk past any existing init calls (selector init etc.) so + // class init runs after them. The order within main's prelude + // doesn't matter functionally (the two caches are independent), + // but stable ordering keeps IR snapshots deterministic. + var insert_before = c.LLVMGetFirstInstruction(entry_bb); + while (insert_before != null) : (insert_before = c.LLVMGetNextInstruction(insert_before)) { + if (c.LLVMGetInstructionOpcode(insert_before) != c.LLVMCall) break; + } + if (insert_before != null) { + c.LLVMPositionBuilderBefore(self.e.builder, insert_before); + } else { + c.LLVMPositionBuilderAtEnd(self.e.builder, entry_bb); + } + var no_args: [0]c.LLVMValueRef = .{}; + _ = c.LLVMBuildCall2(self.e.builder, ctor_ty, ctor, &no_args, 0, ""); + } + } + + /// M1.2 A.4 — emit class-pair registration constructor for every + /// sx-defined `#objc_class` declaration. Same shape as the Phase + /// 3.1 `emitObjcClassInit` companion: a `@llvm.global_ctors`- + /// registered constructor that runs at module load AND gets + /// injected at the top of `main` for the ORC JIT path (which + /// doesn't honor `@llvm.global_ctors`). + /// + /// For each entry in `objc_defined_class_cache`: + /// super_cls = objc_getClass("") // default NSObject + /// cls = objc_allocateClassPair(super_cls, "", 0) + /// class_addIvar(cls, "__sx_state", 8, 3, "^v") // M1.2 A.4b.i + /// objc_registerClassPair(cls) + /// g__state_ivar = class_getInstanceVariable(cls, "__sx_state") + /// + /// Method IMPs (`class_addMethod`) and the `+alloc` / `-dealloc` + /// overrides come in A.4b.ii / A.5 / A.6. + pub fn emitObjcDefinedClassInit(self: FfiCtors) void { + if (self.e.ir_mod.objc_defined_class_cache.items.len == 0) return; + + const ptr_ty = self.e.cached_ptr; + const i32_ty = self.e.cached_i32; + const i64_ty = self.e.cached_i64; + const i8_ty = c.LLVMInt8TypeInContext(self.e.context); + + // Lazy-declare the Obj-C runtime APIs the constructor calls. + // objc_getClass(name: *u8) -> *void. + const get_class_fn, const get_class_ty = self.e.lazyDeclareCRuntime("objc_getClass", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); + // objc_allocateClassPair(super: *void, name: *u8, extra: usize) -> *void. + const alloc_pair_fn, const alloc_pair_ty = self.e.lazyDeclareCRuntime("objc_allocateClassPair", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty }, ptr_ty, 0); + // class_addIvar(cls: *void, name: *u8, size: u64, log2align: u8, type: *u8) -> bool. + const add_ivar_fn, const add_ivar_ty = self.e.lazyDeclareCRuntime("class_addIvar", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty, i8_ty, ptr_ty }, i8_ty, 0); + // sel_registerName(name: *u8) -> *void. + const sel_reg_fn, const sel_reg_ty = self.e.lazyDeclareCRuntime("sel_registerName", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); + // class_addMethod(cls: *void, sel: *void, imp: *void, types: *u8) -> bool. + const add_method_fn, const add_method_ty = self.e.lazyDeclareCRuntime("class_addMethod", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, ptr_ty, ptr_ty }, i8_ty, 0); + // objc_registerClassPair(cls: *void) -> void. + const register_fn, const register_ty = self.e.lazyDeclareCRuntime("objc_registerClassPair", &[_]c.LLVMTypeRef{ptr_ty}, self.e.cached_void, 0); + // class_getInstanceVariable(cls: *void, name: *u8) -> *Ivar. + const get_iv_fn, const get_iv_ty = self.e.lazyDeclareCRuntime("class_getInstanceVariable", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty }, ptr_ty, 0); + + // Constructor: void __sx_objc_defined_class_init(). + var no_params: [0]c.LLVMTypeRef = .{}; + const ctor_ty = c.LLVMFunctionType(self.e.cached_void, &no_params, 0, 0); + const ctor = c.LLVMAddFunction(self.e.llvm_module, "__sx_objc_defined_class_init", ctor_ty); + c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage); + const entry = c.LLVMAppendBasicBlockInContext(self.e.context, ctor, "entry"); + c.LLVMPositionBuilderAtEnd(self.e.builder, entry); + + // Reusable C-string globals for ivar metadata (same across classes). + const sx_state_name_global = self.e.emitPrivateCString("__sx_state", "OBJC_IVAR_NAME_"); + const sx_state_enc_global = self.e.emitPrivateCString("^v", "OBJC_IVAR_TYPE_"); + + for (self.e.ir_mod.objc_defined_class_cache.items) |entry_kv| { + const fcd = entry_kv.decl; + const class_name = fcd.name; + + // Parent class — pre-resolved Obj-C runtime name from + // lower.zig (M2.3 resolveObjcParentName). Stored on the + // cache entry so emit_llvm doesn't re-walk + // foreign_class_map here. + const parent_name = entry_kv.parent_objc_name; + + const parent_str_global = self.e.emitPrivateCString(parent_name, "OBJC_CLASS_NAME_"); + const class_str_global = self.e.emitPrivateCString(class_name, "OBJC_CLASS_NAME_"); + + // super_cls = objc_getClass("ParentName") + var get_args: [1]c.LLVMValueRef = .{parent_str_global}; + const super_val = c.LLVMBuildCall2(self.e.builder, get_class_ty, get_class_fn, &get_args, 1, "super_cls"); + + // cls = objc_allocateClassPair(super_cls, "ClassName", 0) + var alloc_args: [3]c.LLVMValueRef = .{ super_val, class_str_global, c.LLVMConstInt(i64_ty, 0, 0) }; + const cls_val = c.LLVMBuildCall2(self.e.builder, alloc_pair_ty, alloc_pair_fn, &alloc_args, 3, "cls"); + + // class_addIvar(cls, "__sx_state", 8, 3, "^v") + // size = 8 (pointer) — sizeof(*void) on 64-bit + // log2align = 3 — alignof(*void) = 8 = 2^3 + // type = "^v" (encoded *void) + var ivar_args: [5]c.LLVMValueRef = .{ + cls_val, + sx_state_name_global, + c.LLVMConstInt(i64_ty, 8, 0), + c.LLVMConstInt(i8_ty, 3, 0), + sx_state_enc_global, + }; + _ = c.LLVMBuildCall2(self.e.builder, add_ivar_ty, add_ivar_fn, &ivar_args, 5, ""); + + // Class-method registration (M2.1(b)) and the +alloc IMP + // (M1.2 A.5) both target the metaclass. Compute it once + // up-front so all metaclass-bound class_addMethod calls + // can reference the same LLVM value. + // + // metaclass = object_getClass(cls). (object_getClass on a + // Class returns the metaclass — a Class IS an instance of + // its metaclass. Distinct from objc_getClass(name).) + const obj_get_class_fn, const obj_get_class_ty = self.e.lazyDeclareCRuntime("object_getClass", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); + var ogc_args: [1]c.LLVMValueRef = .{cls_val}; + const metaclass_val = c.LLVMBuildCall2(self.e.builder, obj_get_class_ty, obj_get_class_fn, &ogc_args, 1, "metacls"); + + // class_addMethod(target, sel_registerName(sel), imp, encoding) + // — register each method's IMP trampoline (M1.2 A.4b.iii + // + M2.1(b)). Instance methods register on `cls`; class + // methods (`is_class`) on the metaclass. Must run BEFORE + // objc_registerClassPair; the runtime locks the method + // list at registration time on some SDK versions. + for (entry_kv.methods) |method| { + const sel_str_global = self.e.emitPrivateCString(method.sel, "OBJC_METH_VAR_NAME_"); + const enc_str_global = self.e.emitPrivateCString(method.encoding, "OBJC_METH_VAR_TYPE_"); + + var sel_args: [1]c.LLVMValueRef = .{sel_str_global}; + const sel_val = c.LLVMBuildCall2(self.e.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel"); + + const imp_z = self.e.alloc.dupeZ(u8, method.imp_name) catch continue; + defer self.e.alloc.free(imp_z); + const imp_fn = c.LLVMGetNamedFunction(self.e.llvm_module, imp_z.ptr); + if (imp_fn == null) continue; + + const target_cls = if (method.is_class) metaclass_val else cls_val; + var add_args: [4]c.LLVMValueRef = .{ target_cls, sel_val, imp_fn, enc_str_global }; + _ = c.LLVMBuildCall2(self.e.builder, add_method_ty, add_method_fn, &add_args, 4, ""); + } + + // M2.3 / M3.2 — register `#implements` protocol conformances + // BEFORE objc_registerClassPair. iOS checks + // `class_conformsToProtocol` when instantiating scene + // delegates and other protocol-typed callbacks; without + // these the runtime silently rejects the class. + // + // The protocol may not be present on every SDK / runtime + // (dead-strip pruning, version skew), so `objc_getProtocol` + // returning null is non-fatal — skip the addProtocol call. + const get_proto_fn, const get_proto_ty = self.e.lazyDeclareCRuntime("objc_getProtocol", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); + const add_proto_fn, const add_proto_ty = self.e.lazyDeclareCRuntime("class_addProtocol", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty }, i8_ty, 0); + for (fcd.members) |m| switch (m) { + .implements => |proto_alias| { + const proto_str_global = self.e.emitPrivateCString(proto_alias, "OBJC_PROTOCOL_NAME_"); + var gp_args: [1]c.LLVMValueRef = .{proto_str_global}; + const proto_val = c.LLVMBuildCall2(self.e.builder, get_proto_ty, get_proto_fn, &gp_args, 1, "proto"); + var ap_args: [2]c.LLVMValueRef = .{ cls_val, proto_val }; + _ = c.LLVMBuildCall2(self.e.builder, add_proto_ty, add_proto_fn, &ap_args, 2, ""); + }, + else => {}, + }; + + // objc_registerClassPair(cls) + var reg_args: [1]c.LLVMValueRef = .{cls_val}; + _ = c.LLVMBuildCall2(self.e.builder, register_ty, register_fn, ®_args, 1, ""); + + // Cache the class pointer in `___class` global so the + // synthesized -dealloc trampoline (M1.2 A.6) can use it for + // [super dealloc] dispatch via objc_msgSendSuper2. + const class_global_name = std.fmt.allocPrint(self.e.alloc, "__{s}_class", .{class_name}) catch continue; + defer self.e.alloc.free(class_global_name); + const class_global_z = self.e.alloc.dupeZ(u8, class_global_name) catch continue; + defer self.e.alloc.free(class_global_z); + const class_global = c.LLVMGetNamedGlobal(self.e.llvm_module, class_global_z.ptr); + if (class_global != null) { + _ = c.LLVMBuildStore(self.e.builder, cls_val, class_global); + } + + // M1.2 A.6 — register the synthesized `-dealloc` IMP on the + // class itself (instance method). The runtime fires it at + // refcount-zero; the IMP frees __sx_state and chains to + // [super dealloc]. + const dealloc_imp_name = std.fmt.allocPrint(self.e.alloc, "__{s}_dealloc_imp", .{class_name}) catch continue; + defer self.e.alloc.free(dealloc_imp_name); + const dealloc_imp_z = self.e.alloc.dupeZ(u8, dealloc_imp_name) catch continue; + defer self.e.alloc.free(dealloc_imp_z); + const dealloc_imp_fn = c.LLVMGetNamedFunction(self.e.llvm_module, dealloc_imp_z.ptr); + if (dealloc_imp_fn != null) { + const dealloc_sel_global = self.e.emitPrivateCString("dealloc", "OBJC_METH_VAR_NAME_"); + const dealloc_enc_global = self.e.emitPrivateCString("v@:", "OBJC_METH_VAR_TYPE_"); + + var sel_args: [1]c.LLVMValueRef = .{dealloc_sel_global}; + const sel_val = c.LLVMBuildCall2(self.e.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel_dealloc"); + + var add_args: [4]c.LLVMValueRef = .{ cls_val, sel_val, dealloc_imp_fn, dealloc_enc_global }; + _ = c.LLVMBuildCall2(self.e.builder, add_method_ty, add_method_fn, &add_args, 4, ""); + } + + // M1.2 A.5 — register the synthesized `+alloc` IMP on the + // metaclass. Class methods live on the metaclass (every + // Class object's `isa` points to the metaclass), so we + // resolve it via `object_getClass(cls)` and `class_addMethod` + // the IMP there. Encoding `@@:` = returns id, takes Class, + // then SEL — Apple's standard `+alloc` shape. This override + // wins over NSObject's default +alloc; runtime instantiations + // (UIKit, Info.plist, NSCoder) go through our IMP and get the + // __sx_state ivar bound. + const alloc_imp_name = std.fmt.allocPrint(self.e.alloc, "__{s}_alloc_imp", .{class_name}) catch continue; + defer self.e.alloc.free(alloc_imp_name); + const alloc_imp_z = self.e.alloc.dupeZ(u8, alloc_imp_name) catch continue; + defer self.e.alloc.free(alloc_imp_z); + const alloc_imp_fn = c.LLVMGetNamedFunction(self.e.llvm_module, alloc_imp_z.ptr); + if (alloc_imp_fn != null) { + // metaclass_val was computed up-front above (shared + // with class-method registration). +alloc is a class + // method registered on the metaclass. + const alloc_sel_global = self.e.emitPrivateCString("alloc", "OBJC_METH_VAR_NAME_"); + const alloc_enc_global = self.e.emitPrivateCString("@@:", "OBJC_METH_VAR_TYPE_"); + + var sel_args: [1]c.LLVMValueRef = .{alloc_sel_global}; + const sel_val = c.LLVMBuildCall2(self.e.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel_alloc"); + + var add_args: [4]c.LLVMValueRef = .{ metaclass_val, sel_val, alloc_imp_fn, alloc_enc_global }; + _ = c.LLVMBuildCall2(self.e.builder, add_method_ty, add_method_fn, &add_args, 4, ""); + } + + // Cache the ivar handle in the per-class global so trampolines + // can read the __sx_state ivar without re-looking-it-up. The + // global is declared by lower.zig (M1.2 A.4b.i) and starts as + // null; the constructor fills it in here. + const ivar_global_name = std.fmt.allocPrint(self.e.alloc, "__{s}_state_ivar", .{class_name}) catch continue; + defer self.e.alloc.free(ivar_global_name); + const ivar_global_z = self.e.alloc.dupeZ(u8, ivar_global_name) catch continue; + defer self.e.alloc.free(ivar_global_z); + const ivar_global = c.LLVMGetNamedGlobal(self.e.llvm_module, ivar_global_z.ptr); + if (ivar_global != null) { + var iv_args: [2]c.LLVMValueRef = .{ cls_val, sx_state_name_global }; + const iv_val = c.LLVMBuildCall2(self.e.builder, get_iv_ty, get_iv_fn, &iv_args, 2, "iv"); + _ = c.LLVMBuildStore(self.e.builder, iv_val, ivar_global); + } + } + _ = c.LLVMBuildRetVoid(self.e.builder); + + // Inject the call into main's entry block ONLY — skip + // @llvm.global_ctors. Apple's frameworks (UIKit on iOS, + // AppKit on macOS) register their Obj-C classes during + // dyld's image-init phase, which overlaps global_ctors. If + // we ran there too, `objc_getClass("UIResponder")` would + // return null and `objc_allocateClassPair(null, ...)` would + // crash inside objc_registerClassPair. main's entry runs + // AFTER dyld's framework init is complete but BEFORE user + // code (UIApplicationMain), so the runtime sees the parent + // class properly. + self.e.injectCtorIntoMain(ctor, ctor_ty); + + _ = i32_ty; + } +}; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 2b604fb..689324c 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -14,6 +14,7 @@ const llvm_types = @import("../backend/llvm/types.zig"); const llvm_abi = @import("../backend/llvm/abi.zig"); const llvm_debug = @import("../backend/llvm/debug.zig"); const llvm_reflection = @import("../backend/llvm/reflection.zig"); +const llvm_ffi_ctors = @import("../backend/llvm/ffi_ctors.zig"); const ir_inst = @import("inst.zig"); const Ref = ir_inst.Ref; const Span = ir_inst.Span; @@ -348,7 +349,7 @@ pub const LLVMEmitter = struct { } // Pass 2.5: Emit Obj-C selector init constructor (Phase 1.5). - self.emitObjcSelectorInit(); + self.ffiCtors().emitObjcSelectorInit(); // Pass 2.5b: Emit Obj-C class-pair registration constructor for // sx-defined classes (M1.2 A.4+). Runs BEFORE the foreign @@ -357,12 +358,12 @@ pub const LLVMEmitter = struct { // `objc_getClass(\"SxFoo\")` runs to populate the Phase 3.1 // class-object cache — otherwise the cache slot would store // null and `SxFoo.method()` dispatches against null. - self.emitObjcDefinedClassInit(); + self.ffiCtors().emitObjcDefinedClassInit(); // Pass 2.5c: Emit Obj-C class-object init constructor (Phase 3.1). // Same shape as the selector init — populates the per-module // cached `Class*` slots via `objc_getClass` at module-init time. - self.emitObjcClassInit(); + self.ffiCtors().emitObjcClassInit(); // Pass 2.6: On macOS, chdir to the .app bundle's Resources dir at // startup so relative asset paths work when Finder/`open` @@ -400,482 +401,8 @@ pub const LLVMEmitter = struct { return ""; } - /// Synthesize a module constructor that populates each interned - /// Obj-C selector slot via `sel_registerName`, once at module - /// load. Registered in `@llvm.global_ctors` so dyld / ld.so / the - /// LLVM ORC JIT all run it before main. Per `#objc_call` site - /// collapses to a single load from the slot. - /// - /// We tried clang's section-based shape (`__DATA,__objc_selrefs` + - /// `externally_initialized` linkage, no constructor — dyld - /// resolves at load time) and it works for fully-linked binaries - /// via the system loader, BUT LLVM's ORC JIT (the engine behind - /// `sx run`) doesn't process Mach-O Obj-C metadata sections — - /// the slot stays at its initial value (the method-name string - /// pointer) and `objc_msgSend` dispatches with a bogus SEL. - /// `@llvm.global_ctors` is a portable choice that works both - /// in-JIT and as a linked binary, at the cost of a tiny - /// startup pass (one sel_registerName + store per unique - /// selector). - fn emitObjcSelectorInit(self: *LLVMEmitter) void { - if (self.ir_mod.objc_selector_cache.items.len == 0) return; - - // Lazy-declare sel_registerName for the constructor body — - // lower.zig only declares it when a non-literal selector - // appears, which the constructor doesn't depend on. - const sel_reg_name = "sel_registerName"; - const sel_reg_z = self.alloc.dupeZ(u8, sel_reg_name) catch unreachable; - defer self.alloc.free(sel_reg_z); - var sel_reg_fn = c.LLVMGetNamedFunction(self.llvm_module, sel_reg_z.ptr); - var sel_reg_ty: c.LLVMTypeRef = undefined; - if (sel_reg_fn == null) { - var params: [1]c.LLVMTypeRef = .{self.cached_ptr}; - sel_reg_ty = c.LLVMFunctionType(self.cached_ptr, ¶ms, 1, 0); - sel_reg_fn = c.LLVMAddFunction(self.llvm_module, sel_reg_z.ptr, sel_reg_ty); - c.LLVMSetLinkage(sel_reg_fn, c.LLVMExternalLinkage); - } else { - sel_reg_ty = c.LLVMGlobalGetValueType(sel_reg_fn); - } - - // Constructor: void __sx_objc_selector_init(). - var no_params: [0]c.LLVMTypeRef = .{}; - const ctor_ty = c.LLVMFunctionType(self.cached_void, &no_params, 0, 0); - const ctor = c.LLVMAddFunction(self.llvm_module, "__sx_objc_selector_init", ctor_ty); - c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage); - const entry = c.LLVMAppendBasicBlockInContext(self.context, ctor, "entry"); - c.LLVMPositionBuilderAtEnd(self.builder, entry); - - for (self.ir_mod.objc_selector_cache.items) |entry_kv| { - const sel_str = entry_kv.sel; - const slot_gid = entry_kv.slot; - const slot_global = self.global_map.get(@intCast(slot_gid.index())) orelse continue; - - // Method-name C-string — names match clang's convention - // so debuggers / nm / dyld see the same symbols, even - // though the surrounding section tagging isn't load- - // bearing in our JIT path. - const meth_str_z = self.alloc.allocSentinel(u8, sel_str.len, 0) catch continue; - defer self.alloc.free(meth_str_z); - @memcpy(meth_str_z[0..sel_str.len], sel_str); - const str_const = c.LLVMConstStringInContext(self.context, meth_str_z.ptr, @intCast(sel_str.len), 0); - const str_global = c.LLVMAddGlobal(self.llvm_module, c.LLVMTypeOf(str_const), "OBJC_METH_VAR_NAME_"); - c.LLVMSetInitializer(str_global, str_const); - c.LLVMSetLinkage(str_global, c.LLVMPrivateLinkage); - c.LLVMSetGlobalConstant(str_global, 1); - c.LLVMSetUnnamedAddress(str_global, c.LLVMGlobalUnnamedAddr); - - var sel_args: [1]c.LLVMValueRef = .{str_global}; - const sel_val = c.LLVMBuildCall2(self.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel"); - _ = c.LLVMBuildStore(self.builder, sel_val, slot_global); - } - _ = c.LLVMBuildRetVoid(self.builder); - - // Register the constructor in @llvm.global_ctors. dyld picks - // this up for a fully-linked binary at load time. - const i32_ty = self.cached_i32; - const ptr_ty = self.cached_ptr; - var ctor_field_types: [3]c.LLVMTypeRef = .{ i32_ty, ptr_ty, ptr_ty }; - const ctor_struct_ty = c.LLVMStructTypeInContext(self.context, &ctor_field_types, 3, 0); - var ctor_fields: [3]c.LLVMValueRef = .{ - c.LLVMConstInt(i32_ty, 65535, 0), - ctor, - c.LLVMConstNull(ptr_ty), - }; - const ctor_entry = c.LLVMConstNamedStruct(ctor_struct_ty, &ctor_fields, 3); - const ctors_arr_ty = c.LLVMArrayType2(ctor_struct_ty, 1); - var ctor_entries: [1]c.LLVMValueRef = .{ctor_entry}; - const ctors_init = c.LLVMConstArray2(ctor_struct_ty, &ctor_entries, 1); - const ctors_global = c.LLVMAddGlobal(self.llvm_module, ctors_arr_ty, "llvm.global_ctors"); - c.LLVMSetInitializer(ctors_global, ctors_init); - c.LLVMSetLinkage(ctors_global, c.LLVMAppendingLinkage); - - // BUT — LLVM's ORC JIT (the engine for `sx run`) doesn't - // automatically run `@llvm.global_ctors`. Inject a direct - // call from `main`'s entry block as well; idempotent under - // dyld (sel_registerName returns the same SEL on second call). - const main_z = "main"; - const main_fn = c.LLVMGetNamedFunction(self.llvm_module, main_z); - if (main_fn != null) { - const entry_bb = c.LLVMGetEntryBasicBlock(main_fn); - const first_inst = c.LLVMGetFirstInstruction(entry_bb); - if (first_inst != null) { - c.LLVMPositionBuilderBefore(self.builder, first_inst); - } else { - c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); - } - var no_args: [0]c.LLVMValueRef = .{}; - _ = c.LLVMBuildCall2(self.builder, ctor_ty, ctor, &no_args, 0, ""); - } - } - - /// Phase 3.1 companion to `emitObjcSelectorInit`. Walks - /// `module.objc_class_cache` and synthesizes a constructor that - /// populates each cached `Class*` slot via `objc_getClass(name)` - /// exactly once at module-init. Registered in `@llvm.global_ctors` - /// AND injected at the top of `main()` for the ORC JIT path. - fn emitObjcClassInit(self: *LLVMEmitter) void { - if (self.ir_mod.objc_class_cache.items.len == 0) return; - - // Lazy-declare objc_getClass(name: *u8) -> *void. - const get_class_name = "objc_getClass"; - const get_class_z = self.alloc.dupeZ(u8, get_class_name) catch unreachable; - defer self.alloc.free(get_class_z); - var get_class_fn = c.LLVMGetNamedFunction(self.llvm_module, get_class_z.ptr); - var get_class_ty: c.LLVMTypeRef = undefined; - if (get_class_fn == null) { - var params: [1]c.LLVMTypeRef = .{self.cached_ptr}; - get_class_ty = c.LLVMFunctionType(self.cached_ptr, ¶ms, 1, 0); - get_class_fn = c.LLVMAddFunction(self.llvm_module, get_class_z.ptr, get_class_ty); - c.LLVMSetLinkage(get_class_fn, c.LLVMExternalLinkage); - } else { - get_class_ty = c.LLVMGlobalGetValueType(get_class_fn); - } - - // Constructor: void __sx_objc_class_init(). - var no_params: [0]c.LLVMTypeRef = .{}; - const ctor_ty = c.LLVMFunctionType(self.cached_void, &no_params, 0, 0); - const ctor = c.LLVMAddFunction(self.llvm_module, "__sx_objc_class_init", ctor_ty); - c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage); - const entry = c.LLVMAppendBasicBlockInContext(self.context, ctor, "entry"); - c.LLVMPositionBuilderAtEnd(self.builder, entry); - - for (self.ir_mod.objc_class_cache.items) |entry_kv| { - const class_name = entry_kv.name; - const slot_gid = entry_kv.slot; - const slot_global = self.global_map.get(@intCast(slot_gid.index())) orelse continue; - - // Class-name C-string. - const name_z = self.alloc.allocSentinel(u8, class_name.len, 0) catch continue; - defer self.alloc.free(name_z); - @memcpy(name_z[0..class_name.len], class_name); - const str_const = c.LLVMConstStringInContext(self.context, name_z.ptr, @intCast(class_name.len), 0); - const str_global = c.LLVMAddGlobal(self.llvm_module, c.LLVMTypeOf(str_const), "OBJC_CLASS_NAME_"); - c.LLVMSetInitializer(str_global, str_const); - c.LLVMSetLinkage(str_global, c.LLVMPrivateLinkage); - c.LLVMSetGlobalConstant(str_global, 1); - c.LLVMSetUnnamedAddress(str_global, c.LLVMGlobalUnnamedAddr); - - var call_args: [1]c.LLVMValueRef = .{str_global}; - const class_val = c.LLVMBuildCall2(self.builder, get_class_ty, get_class_fn, &call_args, 1, "cls"); - _ = c.LLVMBuildStore(self.builder, class_val, slot_global); - } - _ = c.LLVMBuildRetVoid(self.builder); - - // Register in @llvm.global_ctors for AOT + inject into main for ORC JIT. - const i32_ty = self.cached_i32; - const ptr_ty = self.cached_ptr; - var ctor_field_types: [3]c.LLVMTypeRef = .{ i32_ty, ptr_ty, ptr_ty }; - const ctor_struct_ty = c.LLVMStructTypeInContext(self.context, &ctor_field_types, 3, 0); - var ctor_fields: [3]c.LLVMValueRef = .{ - c.LLVMConstInt(i32_ty, 65535, 0), - ctor, - c.LLVMConstNull(ptr_ty), - }; - const ctor_entry = c.LLVMConstNamedStruct(ctor_struct_ty, &ctor_fields, 3); - - // Append-vs-replace the existing global_ctors. Selector init may - // have created `@llvm.global_ctors` already — extend its array - // rather than overwriting. - const existing_z = "llvm.global_ctors"; - const existing = c.LLVMGetNamedGlobal(self.llvm_module, existing_z); - if (existing != null) { - const existing_init = c.LLVMGetInitializer(existing); - const existing_arr_ty = c.LLVMGlobalGetValueType(existing); - const old_count = c.LLVMGetArrayLength(existing_arr_ty); - const new_count: c_uint = old_count + 1; - var new_entries = std.ArrayList(c.LLVMValueRef).empty; - defer new_entries.deinit(self.alloc); - var i: c_uint = 0; - while (i < old_count) : (i += 1) { - new_entries.append(self.alloc, c.LLVMGetAggregateElement(existing_init, i)) catch unreachable; - } - new_entries.append(self.alloc, ctor_entry) catch unreachable; - const new_arr_ty = c.LLVMArrayType2(ctor_struct_ty, new_count); - const new_init = c.LLVMConstArray2(ctor_struct_ty, new_entries.items.ptr, new_count); - const new_global = c.LLVMAddGlobal(self.llvm_module, new_arr_ty, "llvm.global_ctors.new"); - c.LLVMSetInitializer(new_global, new_init); - c.LLVMSetLinkage(new_global, c.LLVMAppendingLinkage); - c.LLVMSetValueName2(existing, "llvm.global_ctors.old", "llvm.global_ctors.old".len); - c.LLVMSetValueName2(new_global, "llvm.global_ctors", "llvm.global_ctors".len); - c.LLVMDeleteGlobal(existing); - } else { - const ctors_arr_ty = c.LLVMArrayType2(ctor_struct_ty, 1); - var ctor_entries: [1]c.LLVMValueRef = .{ctor_entry}; - const ctors_init = c.LLVMConstArray2(ctor_struct_ty, &ctor_entries, 1); - const ctors_global = c.LLVMAddGlobal(self.llvm_module, ctors_arr_ty, "llvm.global_ctors"); - c.LLVMSetInitializer(ctors_global, ctors_init); - c.LLVMSetLinkage(ctors_global, c.LLVMAppendingLinkage); - } - - // ORC JIT injection: same trick as emitObjcSelectorInit. Inject a - // direct call from main's entry so the JIT path populates the - // slots too. Must run AFTER the selector init's main injection - // (selectors are needed independently of class objects), so we - // place this call AFTER the first instruction (which is the - // selector-init call, if present) rather than at the very top. - const main_z = "main"; - const main_fn = c.LLVMGetNamedFunction(self.llvm_module, main_z); - if (main_fn != null) { - const entry_bb = c.LLVMGetEntryBasicBlock(main_fn); - // Walk past any existing init calls (selector init etc.) so - // class init runs after them. The order within main's prelude - // doesn't matter functionally (the two caches are independent), - // but stable ordering keeps IR snapshots deterministic. - var insert_before = c.LLVMGetFirstInstruction(entry_bb); - while (insert_before != null) : (insert_before = c.LLVMGetNextInstruction(insert_before)) { - if (c.LLVMGetInstructionOpcode(insert_before) != c.LLVMCall) break; - } - if (insert_before != null) { - c.LLVMPositionBuilderBefore(self.builder, insert_before); - } else { - c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); - } - var no_args: [0]c.LLVMValueRef = .{}; - _ = c.LLVMBuildCall2(self.builder, ctor_ty, ctor, &no_args, 0, ""); - } - } - - /// M1.2 A.4 — emit class-pair registration constructor for every - /// sx-defined `#objc_class` declaration. Same shape as the Phase - /// 3.1 `emitObjcClassInit` companion: a `@llvm.global_ctors`- - /// registered constructor that runs at module load AND gets - /// injected at the top of `main` for the ORC JIT path (which - /// doesn't honor `@llvm.global_ctors`). - /// - /// For each entry in `objc_defined_class_cache`: - /// super_cls = objc_getClass("") // default NSObject - /// cls = objc_allocateClassPair(super_cls, "", 0) - /// class_addIvar(cls, "__sx_state", 8, 3, "^v") // M1.2 A.4b.i - /// objc_registerClassPair(cls) - /// g__state_ivar = class_getInstanceVariable(cls, "__sx_state") - /// - /// Method IMPs (`class_addMethod`) and the `+alloc` / `-dealloc` - /// overrides come in A.4b.ii / A.5 / A.6. - fn emitObjcDefinedClassInit(self: *LLVMEmitter) void { - if (self.ir_mod.objc_defined_class_cache.items.len == 0) return; - - const ptr_ty = self.cached_ptr; - const i32_ty = self.cached_i32; - const i64_ty = self.cached_i64; - const i8_ty = c.LLVMInt8TypeInContext(self.context); - - // Lazy-declare the Obj-C runtime APIs the constructor calls. - // objc_getClass(name: *u8) -> *void. - const get_class_fn, const get_class_ty = self.lazyDeclareCRuntime("objc_getClass", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); - // objc_allocateClassPair(super: *void, name: *u8, extra: usize) -> *void. - const alloc_pair_fn, const alloc_pair_ty = self.lazyDeclareCRuntime("objc_allocateClassPair", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty }, ptr_ty, 0); - // class_addIvar(cls: *void, name: *u8, size: u64, log2align: u8, type: *u8) -> bool. - const add_ivar_fn, const add_ivar_ty = self.lazyDeclareCRuntime("class_addIvar", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty, i8_ty, ptr_ty }, i8_ty, 0); - // sel_registerName(name: *u8) -> *void. - const sel_reg_fn, const sel_reg_ty = self.lazyDeclareCRuntime("sel_registerName", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); - // class_addMethod(cls: *void, sel: *void, imp: *void, types: *u8) -> bool. - const add_method_fn, const add_method_ty = self.lazyDeclareCRuntime("class_addMethod", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, ptr_ty, ptr_ty }, i8_ty, 0); - // objc_registerClassPair(cls: *void) -> void. - const register_fn, const register_ty = self.lazyDeclareCRuntime("objc_registerClassPair", &[_]c.LLVMTypeRef{ptr_ty}, self.cached_void, 0); - // class_getInstanceVariable(cls: *void, name: *u8) -> *Ivar. - const get_iv_fn, const get_iv_ty = self.lazyDeclareCRuntime("class_getInstanceVariable", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty }, ptr_ty, 0); - - // Constructor: void __sx_objc_defined_class_init(). - var no_params: [0]c.LLVMTypeRef = .{}; - const ctor_ty = c.LLVMFunctionType(self.cached_void, &no_params, 0, 0); - const ctor = c.LLVMAddFunction(self.llvm_module, "__sx_objc_defined_class_init", ctor_ty); - c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage); - const entry = c.LLVMAppendBasicBlockInContext(self.context, ctor, "entry"); - c.LLVMPositionBuilderAtEnd(self.builder, entry); - - // Reusable C-string globals for ivar metadata (same across classes). - const sx_state_name_global = self.emitPrivateCString("__sx_state", "OBJC_IVAR_NAME_"); - const sx_state_enc_global = self.emitPrivateCString("^v", "OBJC_IVAR_TYPE_"); - - for (self.ir_mod.objc_defined_class_cache.items) |entry_kv| { - const fcd = entry_kv.decl; - const class_name = fcd.name; - - // Parent class — pre-resolved Obj-C runtime name from - // lower.zig (M2.3 resolveObjcParentName). Stored on the - // cache entry so emit_llvm doesn't re-walk - // foreign_class_map here. - const parent_name = entry_kv.parent_objc_name; - - const parent_str_global = self.emitPrivateCString(parent_name, "OBJC_CLASS_NAME_"); - const class_str_global = self.emitPrivateCString(class_name, "OBJC_CLASS_NAME_"); - - // super_cls = objc_getClass("ParentName") - var get_args: [1]c.LLVMValueRef = .{parent_str_global}; - const super_val = c.LLVMBuildCall2(self.builder, get_class_ty, get_class_fn, &get_args, 1, "super_cls"); - - // cls = objc_allocateClassPair(super_cls, "ClassName", 0) - var alloc_args: [3]c.LLVMValueRef = .{ super_val, class_str_global, c.LLVMConstInt(i64_ty, 0, 0) }; - const cls_val = c.LLVMBuildCall2(self.builder, alloc_pair_ty, alloc_pair_fn, &alloc_args, 3, "cls"); - - // class_addIvar(cls, "__sx_state", 8, 3, "^v") - // size = 8 (pointer) — sizeof(*void) on 64-bit - // log2align = 3 — alignof(*void) = 8 = 2^3 - // type = "^v" (encoded *void) - var ivar_args: [5]c.LLVMValueRef = .{ - cls_val, - sx_state_name_global, - c.LLVMConstInt(i64_ty, 8, 0), - c.LLVMConstInt(i8_ty, 3, 0), - sx_state_enc_global, - }; - _ = c.LLVMBuildCall2(self.builder, add_ivar_ty, add_ivar_fn, &ivar_args, 5, ""); - - // Class-method registration (M2.1(b)) and the +alloc IMP - // (M1.2 A.5) both target the metaclass. Compute it once - // up-front so all metaclass-bound class_addMethod calls - // can reference the same LLVM value. - // - // metaclass = object_getClass(cls). (object_getClass on a - // Class returns the metaclass — a Class IS an instance of - // its metaclass. Distinct from objc_getClass(name).) - const obj_get_class_fn, const obj_get_class_ty = self.lazyDeclareCRuntime("object_getClass", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); - var ogc_args: [1]c.LLVMValueRef = .{cls_val}; - const metaclass_val = c.LLVMBuildCall2(self.builder, obj_get_class_ty, obj_get_class_fn, &ogc_args, 1, "metacls"); - - // class_addMethod(target, sel_registerName(sel), imp, encoding) - // — register each method's IMP trampoline (M1.2 A.4b.iii - // + M2.1(b)). Instance methods register on `cls`; class - // methods (`is_class`) on the metaclass. Must run BEFORE - // objc_registerClassPair; the runtime locks the method - // list at registration time on some SDK versions. - for (entry_kv.methods) |method| { - const sel_str_global = self.emitPrivateCString(method.sel, "OBJC_METH_VAR_NAME_"); - const enc_str_global = self.emitPrivateCString(method.encoding, "OBJC_METH_VAR_TYPE_"); - - var sel_args: [1]c.LLVMValueRef = .{sel_str_global}; - const sel_val = c.LLVMBuildCall2(self.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel"); - - const imp_z = self.alloc.dupeZ(u8, method.imp_name) catch continue; - defer self.alloc.free(imp_z); - const imp_fn = c.LLVMGetNamedFunction(self.llvm_module, imp_z.ptr); - if (imp_fn == null) continue; - - const target_cls = if (method.is_class) metaclass_val else cls_val; - var add_args: [4]c.LLVMValueRef = .{ target_cls, sel_val, imp_fn, enc_str_global }; - _ = c.LLVMBuildCall2(self.builder, add_method_ty, add_method_fn, &add_args, 4, ""); - } - - // M2.3 / M3.2 — register `#implements` protocol conformances - // BEFORE objc_registerClassPair. iOS checks - // `class_conformsToProtocol` when instantiating scene - // delegates and other protocol-typed callbacks; without - // these the runtime silently rejects the class. - // - // The protocol may not be present on every SDK / runtime - // (dead-strip pruning, version skew), so `objc_getProtocol` - // returning null is non-fatal — skip the addProtocol call. - const get_proto_fn, const get_proto_ty = self.lazyDeclareCRuntime("objc_getProtocol", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0); - const add_proto_fn, const add_proto_ty = self.lazyDeclareCRuntime("class_addProtocol", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty }, i8_ty, 0); - for (fcd.members) |m| switch (m) { - .implements => |proto_alias| { - const proto_str_global = self.emitPrivateCString(proto_alias, "OBJC_PROTOCOL_NAME_"); - var gp_args: [1]c.LLVMValueRef = .{proto_str_global}; - const proto_val = c.LLVMBuildCall2(self.builder, get_proto_ty, get_proto_fn, &gp_args, 1, "proto"); - var ap_args: [2]c.LLVMValueRef = .{ cls_val, proto_val }; - _ = c.LLVMBuildCall2(self.builder, add_proto_ty, add_proto_fn, &ap_args, 2, ""); - }, - else => {}, - }; - - // objc_registerClassPair(cls) - var reg_args: [1]c.LLVMValueRef = .{cls_val}; - _ = c.LLVMBuildCall2(self.builder, register_ty, register_fn, ®_args, 1, ""); - - // Cache the class pointer in `___class` global so the - // synthesized -dealloc trampoline (M1.2 A.6) can use it for - // [super dealloc] dispatch via objc_msgSendSuper2. - const class_global_name = std.fmt.allocPrint(self.alloc, "__{s}_class", .{class_name}) catch continue; - defer self.alloc.free(class_global_name); - const class_global_z = self.alloc.dupeZ(u8, class_global_name) catch continue; - defer self.alloc.free(class_global_z); - const class_global = c.LLVMGetNamedGlobal(self.llvm_module, class_global_z.ptr); - if (class_global != null) { - _ = c.LLVMBuildStore(self.builder, cls_val, class_global); - } - - // M1.2 A.6 — register the synthesized `-dealloc` IMP on the - // class itself (instance method). The runtime fires it at - // refcount-zero; the IMP frees __sx_state and chains to - // [super dealloc]. - const dealloc_imp_name = std.fmt.allocPrint(self.alloc, "__{s}_dealloc_imp", .{class_name}) catch continue; - defer self.alloc.free(dealloc_imp_name); - const dealloc_imp_z = self.alloc.dupeZ(u8, dealloc_imp_name) catch continue; - defer self.alloc.free(dealloc_imp_z); - const dealloc_imp_fn = c.LLVMGetNamedFunction(self.llvm_module, dealloc_imp_z.ptr); - if (dealloc_imp_fn != null) { - const dealloc_sel_global = self.emitPrivateCString("dealloc", "OBJC_METH_VAR_NAME_"); - const dealloc_enc_global = self.emitPrivateCString("v@:", "OBJC_METH_VAR_TYPE_"); - - var sel_args: [1]c.LLVMValueRef = .{dealloc_sel_global}; - const sel_val = c.LLVMBuildCall2(self.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel_dealloc"); - - var add_args: [4]c.LLVMValueRef = .{ cls_val, sel_val, dealloc_imp_fn, dealloc_enc_global }; - _ = c.LLVMBuildCall2(self.builder, add_method_ty, add_method_fn, &add_args, 4, ""); - } - - // M1.2 A.5 — register the synthesized `+alloc` IMP on the - // metaclass. Class methods live on the metaclass (every - // Class object's `isa` points to the metaclass), so we - // resolve it via `object_getClass(cls)` and `class_addMethod` - // the IMP there. Encoding `@@:` = returns id, takes Class, - // then SEL — Apple's standard `+alloc` shape. This override - // wins over NSObject's default +alloc; runtime instantiations - // (UIKit, Info.plist, NSCoder) go through our IMP and get the - // __sx_state ivar bound. - const alloc_imp_name = std.fmt.allocPrint(self.alloc, "__{s}_alloc_imp", .{class_name}) catch continue; - defer self.alloc.free(alloc_imp_name); - const alloc_imp_z = self.alloc.dupeZ(u8, alloc_imp_name) catch continue; - defer self.alloc.free(alloc_imp_z); - const alloc_imp_fn = c.LLVMGetNamedFunction(self.llvm_module, alloc_imp_z.ptr); - if (alloc_imp_fn != null) { - // metaclass_val was computed up-front above (shared - // with class-method registration). +alloc is a class - // method registered on the metaclass. - const alloc_sel_global = self.emitPrivateCString("alloc", "OBJC_METH_VAR_NAME_"); - const alloc_enc_global = self.emitPrivateCString("@@:", "OBJC_METH_VAR_TYPE_"); - - var sel_args: [1]c.LLVMValueRef = .{alloc_sel_global}; - const sel_val = c.LLVMBuildCall2(self.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel_alloc"); - - var add_args: [4]c.LLVMValueRef = .{ metaclass_val, sel_val, alloc_imp_fn, alloc_enc_global }; - _ = c.LLVMBuildCall2(self.builder, add_method_ty, add_method_fn, &add_args, 4, ""); - } - - // Cache the ivar handle in the per-class global so trampolines - // can read the __sx_state ivar without re-looking-it-up. The - // global is declared by lower.zig (M1.2 A.4b.i) and starts as - // null; the constructor fills it in here. - const ivar_global_name = std.fmt.allocPrint(self.alloc, "__{s}_state_ivar", .{class_name}) catch continue; - defer self.alloc.free(ivar_global_name); - const ivar_global_z = self.alloc.dupeZ(u8, ivar_global_name) catch continue; - defer self.alloc.free(ivar_global_z); - const ivar_global = c.LLVMGetNamedGlobal(self.llvm_module, ivar_global_z.ptr); - if (ivar_global != null) { - var iv_args: [2]c.LLVMValueRef = .{ cls_val, sx_state_name_global }; - const iv_val = c.LLVMBuildCall2(self.builder, get_iv_ty, get_iv_fn, &iv_args, 2, "iv"); - _ = c.LLVMBuildStore(self.builder, iv_val, ivar_global); - } - } - _ = c.LLVMBuildRetVoid(self.builder); - - // Inject the call into main's entry block ONLY — skip - // @llvm.global_ctors. Apple's frameworks (UIKit on iOS, - // AppKit on macOS) register their Obj-C classes during - // dyld's image-init phase, which overlaps global_ctors. If - // we ran there too, `objc_getClass(\"UIResponder\")` would - // return null and `objc_allocateClassPair(null, ...)` would - // crash inside objc_registerClassPair. main's entry runs - // AFTER dyld's framework init is complete but BEFORE user - // code (UIApplicationMain), so the runtime sees the parent - // class properly. - self.injectCtorIntoMain(ctor, ctor_ty); - - _ = i32_ty; - } - /// Lazy-declare an extern C runtime function. Returns (fn-value, fn-type). - fn lazyDeclareCRuntime(self: *LLVMEmitter, name: []const u8, params: []const c.LLVMTypeRef, ret_ty: c.LLVMTypeRef, is_var_arg: c_int) struct { c.LLVMValueRef, c.LLVMTypeRef } { + pub fn lazyDeclareCRuntime(self: *LLVMEmitter, name: []const u8, params: []const c.LLVMTypeRef, ret_ty: c.LLVMTypeRef, is_var_arg: c_int) struct { c.LLVMValueRef, c.LLVMTypeRef } { const name_z = self.alloc.dupeZ(u8, name) catch unreachable; defer self.alloc.free(name_z); var fn_value = c.LLVMGetNamedFunction(self.llvm_module, name_z.ptr); @@ -892,7 +419,7 @@ pub const LLVMEmitter = struct { /// Emit a private constant C string global. Used for class names, /// selector names, etc. consumed by the Obj-C runtime. - fn emitPrivateCString(self: *LLVMEmitter, s: []const u8, name_hint: []const u8) c.LLVMValueRef { + pub fn emitPrivateCString(self: *LLVMEmitter, s: []const u8, name_hint: []const u8) c.LLVMValueRef { const s_z = self.alloc.allocSentinel(u8, s.len, 0) catch unreachable; defer self.alloc.free(s_z); @memcpy(s_z[0..s.len], s); @@ -917,7 +444,7 @@ pub const LLVMEmitter = struct { /// load — global_ctors is too early because Apple frameworks /// (UIKit etc.) register their Obj-C classes during their own /// init phase that overlaps ours. - fn injectCtorIntoMain(self: *LLVMEmitter, ctor: c.LLVMValueRef, ctor_ty: c.LLVMTypeRef) void { + pub fn injectCtorIntoMain(self: *LLVMEmitter, ctor: c.LLVMValueRef, ctor_ty: c.LLVMTypeRef) void { const main_z = "main"; const main_fn = c.LLVMGetNamedFunction(self.llvm_module, main_z); if (main_fn == null) return; @@ -4433,6 +3960,10 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } + fn ffiCtors(self: *LLVMEmitter) llvm_ffi_ctors.FfiCtors { + return .{ .e = self }; + } + /// IR-type → LLVM-type lowering lives in `backend/llvm/types.zig` /// (`TypeLowering`). This stays the facade entry point (~97 callers). pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { From 2f7c99fd11ebabea370adade9bbf5deb407352fd Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 10:42:58 +0300 Subject: [PATCH 09/26] refactor(backend): extract JNI slot cache into ffi_ctors.zig (A7.3 slice 2a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move getOrCreateJniSlots (the cls/methodid slot-cache builder) out of emit_llvm.zig into the FfiCtors backend *LLVMEmitter facade. Behavior-preserving — self.* -> self.e.* only. - FfiCtors gains getOrCreateJniSlots (pub). The jni_slots cache + mangleJniKey stay on LLVMEmitter; mangleJniKey is widened to pub (the facade calls it back, like lazyDeclareCRuntime/emitPrivateCString), and JniSlotPair is widened to pub (the facade returns it; the call site consumes it). 1 call site routed via ffiCtors(). - emitJniConstructor intentionally NOT moved in this slice: it is emission-heavy (resolveRef/mapRef/coerceArg/getRefIRType/extractSlicePtr/loadJniFn/ emitCStringGlobal — 100+ internal callers for the first two), so relocating it would pub-expose the emitter's core value-emission machinery. Consistent with A7.2 keeping emitFieldValueGet in emit_llvm.zig. Pending an explicit decision. Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (JNI anchors 1402/1408/1418/1425 green, no churn). --- src/backend/llvm/ffi_ctors.zig | 33 ++++++++++++++++++++++++++++++ src/ir/emit_llvm.zig | 37 +++------------------------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/backend/llvm/ffi_ctors.zig b/src/backend/llvm/ffi_ctors.zig index 5bfee0d..56df039 100644 --- a/src/backend/llvm/ffi_ctors.zig +++ b/src/backend/llvm/ffi_ctors.zig @@ -4,6 +4,7 @@ const c = llvm.c; const emit = @import("../../ir/emit_llvm.zig"); const LLVMEmitter = emit.LLVMEmitter; +const JniSlotPair = LLVMEmitter.JniSlotPair; /// Obj-C / JNI runtime-constructor emission (architecture phase A7.3), extracted /// from `LLVMEmitter`. A backend `*LLVMEmitter` facade (field `e`): it builds the @@ -473,4 +474,36 @@ pub const FfiCtors = struct { _ = i32_ty; } + + /// Return `{cls_slot, mid_slot}` global pair for the + /// `(name, sig)` literal — created on first lookup, shared across + /// later `#jni_call` sites with the same literal pair. Both + /// slots are zero-initialized `ptr`; the call-site lowering does + /// lazy population on first dispatch. The cache (`jni_slots`) + + /// `mangleJniKey` stay on `LLVMEmitter`. + pub fn getOrCreateJniSlots(self: FfiCtors, name: []const u8, sig: []const u8) JniSlotPair { + // Compose the key from name + a separator + sig. The separator + // is a byte that can't appear in a JNI method name or signature + // (NUL), so the same key never collides across distinct pairs. + const key = std.fmt.allocPrint(self.e.alloc, "{s}\x00{s}", .{ name, sig }) catch unreachable; + if (self.e.jni_slots.get(key)) |existing| { + self.e.alloc.free(key); + return existing; + } + const mangled = self.e.mangleJniKey(name, sig); + defer self.e.alloc.free(mangled); + const cls_name = std.fmt.allocPrintSentinel(self.e.alloc, "SX_JNI_CLS_{s}", .{mangled}, 0) catch unreachable; + defer self.e.alloc.free(cls_name); + const mid_name = std.fmt.allocPrintSentinel(self.e.alloc, "SX_JNI_MID_{s}", .{mangled}, 0) catch unreachable; + defer self.e.alloc.free(mid_name); + const cls_slot = c.LLVMAddGlobal(self.e.llvm_module, self.e.cached_ptr, cls_name.ptr); + c.LLVMSetLinkage(cls_slot, c.LLVMInternalLinkage); + c.LLVMSetInitializer(cls_slot, c.LLVMConstNull(self.e.cached_ptr)); + const mid_slot = c.LLVMAddGlobal(self.e.llvm_module, self.e.cached_ptr, mid_name.ptr); + c.LLVMSetLinkage(mid_slot, c.LLVMInternalLinkage); + c.LLVMSetInitializer(mid_slot, c.LLVMConstNull(self.e.cached_ptr)); + const pair = JniSlotPair{ .cls_slot = cls_slot, .mid_slot = mid_slot }; + self.e.jni_slots.put(key, pair) catch unreachable; + return pair; + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 689324c..3e20c4e 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -221,7 +221,7 @@ pub const LLVMEmitter = struct { param_index: u32, }; - const JniSlotPair = struct { + pub const JniSlotPair = struct { cls_slot: c.LLVMValueRef, // @SX_JNI_CLS_: ptr (GlobalRef to jclass) mid_slot: c.LLVMValueRef, // @SX_JNI_MID_: ptr (jmethodID) }; @@ -623,43 +623,12 @@ pub const LLVMEmitter = struct { } } - /// Return `{cls_slot, mid_slot}` global pair for the - /// `(name, sig)` literal — created on first lookup, shared across - /// later `#jni_call` sites with the same literal pair. Both - /// slots are zero-initialized `ptr`; the call-site lowering does - /// lazy population on first dispatch. - fn getOrCreateJniSlots(self: *LLVMEmitter, name: []const u8, sig: []const u8) JniSlotPair { - // Compose the key from name + a separator + sig. The separator - // is a byte that can't appear in a JNI method name or signature - // (NUL), so the same key never collides across distinct pairs. - const key = std.fmt.allocPrint(self.alloc, "{s}\x00{s}", .{ name, sig }) catch unreachable; - if (self.jni_slots.get(key)) |existing| { - self.alloc.free(key); - return existing; - } - const mangled = self.mangleJniKey(name, sig); - defer self.alloc.free(mangled); - const cls_name = std.fmt.allocPrintSentinel(self.alloc, "SX_JNI_CLS_{s}", .{mangled}, 0) catch unreachable; - defer self.alloc.free(cls_name); - const mid_name = std.fmt.allocPrintSentinel(self.alloc, "SX_JNI_MID_{s}", .{mangled}, 0) catch unreachable; - defer self.alloc.free(mid_name); - const cls_slot = c.LLVMAddGlobal(self.llvm_module, self.cached_ptr, cls_name.ptr); - c.LLVMSetLinkage(cls_slot, c.LLVMInternalLinkage); - c.LLVMSetInitializer(cls_slot, c.LLVMConstNull(self.cached_ptr)); - const mid_slot = c.LLVMAddGlobal(self.llvm_module, self.cached_ptr, mid_name.ptr); - c.LLVMSetLinkage(mid_slot, c.LLVMInternalLinkage); - c.LLVMSetInitializer(mid_slot, c.LLVMConstNull(self.cached_ptr)); - const pair = JniSlotPair{ .cls_slot = cls_slot, .mid_slot = mid_slot }; - self.jni_slots.put(key, pair) catch unreachable; - return pair; - } - /// Build an LLVM-friendly identifier suffix from a JNI /// `(method_name, signature)` pair. Non-identifier characters are /// rewritten to `_`; the resulting string is unique per pair (the /// caller guarantees uniqueness on `(name, sig)`, which we /// preserve through the separator between mangled name and sig). - fn mangleJniKey(self: *LLVMEmitter, name: []const u8, sig: []const u8) []u8 { + pub fn mangleJniKey(self: *LLVMEmitter, name: []const u8, sig: []const u8) []u8 { var buf = std.ArrayList(u8).empty; for (name) |b| buf.append(self.alloc, if (isIdentByte(b)) b else '_') catch unreachable; buf.appendSlice(self.alloc, "__") catch unreachable; @@ -1894,7 +1863,7 @@ pub const LLVMEmitter = struct { // back to the per-call `GetObjectClass + GetMethodID` // sequence (1.15 shape). const mid = if (msg.cache_key) |ck| blk: { - const pair = self.getOrCreateJniSlots(ck.name_str, ck.sig_str); + const pair = self.ffiCtors().getOrCreateJniSlots(ck.name_str, ck.sig_str); const cached_mid = c.LLVMBuildLoad2(self.builder, self.cached_ptr, pair.mid_slot, "jni.cached.mid"); const is_cached = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cached_mid, c.LLVMConstNull(self.cached_ptr), "jni.is.cached"); From 312d2e90ed1031496c3ae9085c4dcf85b6c41d82 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 11:11:10 +0300 Subject: [PATCH 10/26] refactor(backend): extract scalar instruction handlers into ops.zig (A7.4 slice a) Move the Constants/Arithmetic/Bitwise/Comparisons/Logical opcode handler bodies out of emitInst into a new Ops facade in src/backend/llvm/ops.zig. emitInst's scalar arms now delegate via self.ops().*; the shared infra they call (mapRef/resolveRef/matchBinOpTypes/emitCmp/emitCmpOrdered/emitStrCmp/ emitStringConstant/reflection + isFloatOrVecFloat/isSignedType) stays on LLVMEmitter, widened to pub as needed. Pure relocation: zero snapshot churn. --- src/backend/llvm/ops.zig | 289 +++++++++++++++++++++++++++++++++++++++ src/ir/emit_llvm.zig | 271 +++++++----------------------------- 2 files changed, 339 insertions(+), 221 deletions(-) create mode 100644 src/backend/llvm/ops.zig diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig new file mode 100644 index 0000000..1f627e2 --- /dev/null +++ b/src/backend/llvm/ops.zig @@ -0,0 +1,289 @@ +const std = @import("std"); +const llvm = @import("../../llvm_api.zig"); +const c = llvm.c; +const emit = @import("../../ir/emit_llvm.zig"); +const ir_inst = @import("../../ir/inst.zig"); +const ir_types = @import("../../ir/types.zig"); + +const LLVMEmitter = emit.LLVMEmitter; +const Inst = ir_inst.Inst; +const BinOp = ir_inst.BinOp; +const UnaryOp = ir_inst.UnaryOp; +const TypeId = ir_types.TypeId; +const StringId = ir_types.StringId; + +/// Scalar instruction-emission handlers for `emitInst`: the constant, +/// arithmetic, bitwise, comparison, and logical opcodes. A backend +/// `*LLVMEmitter` facade (field `e`): each method emits one opcode's LLVM IR via +/// `self.e.*`. The shared infra these bodies call back into +/// (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/ +/// `emitStrCmp`/`emitStringConstant`/`reflection`) stays on `LLVMEmitter`. +/// `emitInst`'s scalar arms reach these via `self.ops()`. +pub const Ops = struct { + e: *LLVMEmitter, + + // ── Constants ─────────────────────────────────────────── + pub fn emitConstInt(self: Ops, instruction: *const Inst, val: i64) void { + const ty = self.e.toLLVMType(instruction.ty); + const kind = c.LLVMGetTypeKind(ty); + const llvm_val = if (kind == c.LLVMIntegerTypeKind) + c.LLVMConstInt(ty, @bitCast(val), 1) + else if (kind == c.LLVMPointerTypeKind) + c.LLVMConstNull(ty) + else + // void or other non-integer type: emit i64 0 as unused placeholder + c.LLVMConstInt(c.LLVMInt64TypeInContext(self.e.context), 0, 0); + self.e.mapRef(llvm_val); + } + + pub fn emitConstFloat(self: Ops, instruction: *const Inst, val: f64) void { + const ty = self.e.toLLVMType(instruction.ty); + const llvm_val = c.LLVMConstReal(ty, val); + self.e.mapRef(llvm_val); + } + + pub fn emitConstBool(self: Ops, val: bool) void { + const llvm_val = c.LLVMConstInt(self.e.cached_i1, @intFromBool(val), 0); + self.e.mapRef(llvm_val); + } + + pub fn emitIsComptime(self: Ops) void { + // Compiled code is never the comptime interpreter → constant + // `false`. A `if is_comptime() { … }` branch becomes dead. + self.e.mapRef(c.LLVMConstInt(self.e.cached_i1, 0, 0)); + } + + pub fn emitInterpPrintFrames(self: Ops) void { + // No interpreter stack in compiled code; this only ever sits in + // a dead `is_comptime()` branch. Emit nothing. + self.e.advanceRefCounter(); + } + + pub fn emitTraceFrame(self: Ops, instruction: *const Inst) void { + self.e.mapRef(self.e.reflection().emitTraceFrame(instruction)); + } + + pub fn emitTraceResolve(self: Ops, u: UnaryOp) void { + // The operand is a `Frame*` stamped in by `.trace_frame` (as + // i64); reinterpret and load it. + const raw = self.e.resolveRef(u.operand); + const frame_ty = self.e.getFrameStructType(); + const ptr = c.LLVMBuildIntToPtr(self.e.builder, raw, self.e.cached_ptr, "frame.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, frame_ty, ptr, "frame.val")); + } + + pub fn emitConstString(self: Ops, str_id: StringId) void { + const str = self.e.ir_mod.types.getString(str_id); + const llvm_val = self.e.emitStringConstant(str); + self.e.mapRef(llvm_val); + } + + pub fn emitConstNull(self: Ops, instruction: *const Inst) void { + const ty = if (instruction.ty == .void) self.e.cached_ptr else self.e.toLLVMType(instruction.ty); + const llvm_val = c.LLVMConstNull(ty); + self.e.mapRef(llvm_val); + } + + pub fn emitConstUndef(self: Ops, instruction: *const Inst) void { + if (instruction.ty == .void) { + // void has no value — map to undef i64 as placeholder + self.e.mapRef(c.LLVMGetUndef(self.e.cached_i64)); + } else { + const ty = self.e.toLLVMType(instruction.ty); + const llvm_val = c.LLVMGetUndef(ty); + self.e.mapRef(llvm_val); + } + } + + pub fn emitConstType(self: Ops, tid: TypeId) void { + // Type values are Any-shaped pairs: + // { tag = .any.index() (the meta-marker), + // value = tid.index() } + // Lets storage in Any slots, struct fields, + // `Type`-typed vars, and slice elements all round- + // trip through the standard Any infrastructure. + // `case type:` in `any_to_string` matches on + // tag == `.any.index()`. Runtime `type_name(t)` + // extracts the value field and indexes into the + // type-name lookup table. + const any_ty = self.e.getAnyStructType(); + const tag = c.LLVMConstInt(self.e.cached_i64, TypeId.any.index(), 0); + const val = c.LLVMConstInt(self.e.cached_i64, tid.index(), 0); + var result = c.LLVMGetUndef(any_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, tag, 0, "ct.tag"); + result = c.LLVMBuildInsertValue(self.e.builder, result, val, 1, "ct.val"); + self.e.mapRef(result); + } + + // ── Arithmetic ───────────────────────────────────────── + pub fn emitAdd(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFAdd(self.e.builder, lhs, rhs, "fadd") + else + c.LLVMBuildAdd(self.e.builder, lhs, rhs, "add"); + self.e.mapRef(result); + } + + pub fn emitSub(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFSub(self.e.builder, lhs, rhs, "fsub") + else + c.LLVMBuildSub(self.e.builder, lhs, rhs, "sub"); + self.e.mapRef(result); + } + + pub fn emitMul(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFMul(self.e.builder, lhs, rhs, "fmul") + else + c.LLVMBuildMul(self.e.builder, lhs, rhs, "mul"); + self.e.mapRef(result); + } + + pub fn emitDiv(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFDiv(self.e.builder, lhs, rhs, "fdiv") + else if (emit.isSignedType(instruction.ty)) + c.LLVMBuildSDiv(self.e.builder, lhs, rhs, "sdiv") + else + c.LLVMBuildUDiv(self.e.builder, lhs, rhs, "udiv"); + self.e.mapRef(result); + } + + pub fn emitMod(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFRem(self.e.builder, lhs, rhs, "fmod") + else if (emit.isSignedType(instruction.ty)) + c.LLVMBuildSRem(self.e.builder, lhs, rhs, "srem") + else + c.LLVMBuildURem(self.e.builder, lhs, rhs, "urem"); + self.e.mapRef(result); + } + + pub fn emitNeg(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const operand = self.e.resolveRef(un.operand); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFNeg(self.e.builder, operand, "fneg") + else + c.LLVMBuildNeg(self.e.builder, operand, "neg"); + self.e.mapRef(result); + } + + // ── Bitwise ──────────────────────────────────────────── + pub fn emitBitAnd(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildAnd(self.e.builder, lhs, rhs, "and")); + } + + pub fn emitBitOr(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildOr(self.e.builder, lhs, rhs, "or")); + } + + pub fn emitBitXor(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildXor(self.e.builder, lhs, rhs, "xor")); + } + + pub fn emitBitNot(self: Ops, un: UnaryOp) void { + const operand = self.e.resolveRef(un.operand); + self.e.mapRef(c.LLVMBuildNot(self.e.builder, operand, "not")); + } + + pub fn emitShl(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildShl(self.e.builder, lhs, rhs, "shl")); + } + + pub fn emitShr(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + // Use arithmetic shift right for signed, logical for unsigned + const result = if (emit.isSignedType(instruction.ty)) + c.LLVMBuildAShr(self.e.builder, lhs, rhs, "ashr") + else + c.LLVMBuildLShr(self.e.builder, lhs, rhs, "lshr"); + self.e.mapRef(result); + } + + // ── Comparisons ─────────────────────────────────────── + pub fn emitCmpEq(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmp(bin, instruction.ty, c.LLVMIntEQ, c.LLVMRealOEQ); + } + + pub fn emitCmpNe(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmp(bin, instruction.ty, c.LLVMIntNE, c.LLVMRealONE); + } + + pub fn emitCmpLt(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLT, c.LLVMIntULT, c.LLVMRealOLT); + } + + pub fn emitCmpLe(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLE, c.LLVMIntULE, c.LLVMRealOLE); + } + + pub fn emitCmpGt(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGT, c.LLVMIntUGT, c.LLVMRealOGT); + } + + pub fn emitCmpGe(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGE, c.LLVMIntUGE, c.LLVMRealOGE); + } + + pub fn emitStrEq(self: Ops, bin: BinOp) void { + self.e.emitStrCmp(bin, true); + } + + pub fn emitStrNe(self: Ops, bin: BinOp) void { + self.e.emitStrCmp(bin, false); + } + + // ── Logical ─────────────────────────────────────────── + pub fn emitBoolAnd(self: Ops, bin: BinOp) void { + const lhs = self.e.resolveRef(bin.lhs); + const rhs = self.e.resolveRef(bin.rhs); + self.e.mapRef(c.LLVMBuildAnd(self.e.builder, lhs, rhs, "land")); + } + + pub fn emitBoolOr(self: Ops, bin: BinOp) void { + const lhs = self.e.resolveRef(bin.lhs); + const rhs = self.e.resolveRef(bin.rhs); + self.e.mapRef(c.LLVMBuildOr(self.e.builder, lhs, rhs, "lor")); + } + + pub fn emitBoolNot(self: Ops, un: UnaryOp) void { + const operand = self.e.resolveRef(un.operand); + self.e.mapRef(c.LLVMBuildNot(self.e.builder, operand, "lnot")); + } +}; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 3e20c4e..4c547d1 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -15,6 +15,7 @@ const llvm_abi = @import("../backend/llvm/abi.zig"); const llvm_debug = @import("../backend/llvm/debug.zig"); const llvm_reflection = @import("../backend/llvm/reflection.zig"); const llvm_ffi_ctors = @import("../backend/llvm/ffi_ctors.zig"); +const llvm_ops = @import("../backend/llvm/ops.zig"); const ir_inst = @import("inst.zig"); const Ref = ir_inst.Ref; const Span = ir_inst.Span; @@ -1345,224 +1346,48 @@ pub const LLVMEmitter = struct { self.debugInfo().setInstDebugLocation(instruction.span); switch (instruction.op) { // ── Constants ─────────────────────────────────────────── - .const_int => |val| { - const ty = self.toLLVMType(instruction.ty); - const kind = c.LLVMGetTypeKind(ty); - const llvm_val = if (kind == c.LLVMIntegerTypeKind) - c.LLVMConstInt(ty, @bitCast(val), 1) - else if (kind == c.LLVMPointerTypeKind) - c.LLVMConstNull(ty) - else - // void or other non-integer type: emit i64 0 as unused placeholder - c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0); - self.mapRef(llvm_val); - }, - .const_float => |val| { - const ty = self.toLLVMType(instruction.ty); - const llvm_val = c.LLVMConstReal(ty, val); - self.mapRef(llvm_val); - }, - .const_bool => |val| { - const llvm_val = c.LLVMConstInt(self.cached_i1, @intFromBool(val), 0); - self.mapRef(llvm_val); - }, - .is_comptime => { - // Compiled code is never the comptime interpreter → constant - // `false`. A `if is_comptime() { … }` branch becomes dead. - self.mapRef(c.LLVMConstInt(self.cached_i1, 0, 0)); - }, - .interp_print_frames => { - // No interpreter stack in compiled code; this only ever sits in - // a dead `is_comptime()` branch. Emit nothing. - self.advanceRefCounter(); - }, - .trace_frame => { - self.mapRef(self.reflection().emitTraceFrame(instruction)); - }, - .trace_resolve => |u| { - // The operand is a `Frame*` stamped in by `.trace_frame` (as - // i64); reinterpret and load it. - const raw = self.resolveRef(u.operand); - const frame_ty = self.getFrameStructType(); - const ptr = c.LLVMBuildIntToPtr(self.builder, raw, self.cached_ptr, "frame.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, frame_ty, ptr, "frame.val")); - }, - .const_string => |str_id| { - const str = self.ir_mod.types.getString(str_id); - const llvm_val = self.emitStringConstant(str); - self.mapRef(llvm_val); - }, - .const_null => { - const ty = if (instruction.ty == .void) self.cached_ptr else self.toLLVMType(instruction.ty); - const llvm_val = c.LLVMConstNull(ty); - self.mapRef(llvm_val); - }, - .const_undef => { - if (instruction.ty == .void) { - // void has no value — map to undef i64 as placeholder - self.mapRef(c.LLVMGetUndef(self.cached_i64)); - } else { - const ty = self.toLLVMType(instruction.ty); - const llvm_val = c.LLVMGetUndef(ty); - self.mapRef(llvm_val); - } - }, - .const_type => |tid| { - // Type values are Any-shaped pairs: - // { tag = .any.index() (the meta-marker), - // value = tid.index() } - // Lets storage in Any slots, struct fields, - // `Type`-typed vars, and slice elements all round- - // trip through the standard Any infrastructure. - // `case type:` in `any_to_string` matches on - // tag == `.any.index()`. Runtime `type_name(t)` - // extracts the value field and indexes into the - // type-name lookup table. - const any_ty = self.getAnyStructType(); - const tag = c.LLVMConstInt(self.cached_i64, TypeId.any.index(), 0); - const val = c.LLVMConstInt(self.cached_i64, tid.index(), 0); - var result = c.LLVMGetUndef(any_ty); - result = c.LLVMBuildInsertValue(self.builder, result, tag, 0, "ct.tag"); - result = c.LLVMBuildInsertValue(self.builder, result, val, 1, "ct.val"); - self.mapRef(result); - }, + .const_int => |val| self.ops().emitConstInt(instruction, val), + .const_float => |val| self.ops().emitConstFloat(instruction, val), + .const_bool => |val| self.ops().emitConstBool(val), + .is_comptime => self.ops().emitIsComptime(), + .interp_print_frames => self.ops().emitInterpPrintFrames(), + .trace_frame => self.ops().emitTraceFrame(instruction), + .trace_resolve => |u| self.ops().emitTraceResolve(u), + .const_string => |str_id| self.ops().emitConstString(str_id), + .const_null => self.ops().emitConstNull(instruction), + .const_undef => self.ops().emitConstUndef(instruction), + .const_type => |tid| self.ops().emitConstType(tid), // ── Arithmetic ───────────────────────────────────────── - .add => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types); - const result = if (is_float) - c.LLVMBuildFAdd(self.builder, lhs, rhs, "fadd") - else - c.LLVMBuildAdd(self.builder, lhs, rhs, "add"); - self.mapRef(result); - }, - .sub => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types); - const result = if (is_float) - c.LLVMBuildFSub(self.builder, lhs, rhs, "fsub") - else - c.LLVMBuildSub(self.builder, lhs, rhs, "sub"); - self.mapRef(result); - }, - .mul => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types); - const result = if (is_float) - c.LLVMBuildFMul(self.builder, lhs, rhs, "fmul") - else - c.LLVMBuildMul(self.builder, lhs, rhs, "mul"); - self.mapRef(result); - }, - .div => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types); - const result = if (is_float) - c.LLVMBuildFDiv(self.builder, lhs, rhs, "fdiv") - else if (isSignedType(instruction.ty)) - c.LLVMBuildSDiv(self.builder, lhs, rhs, "sdiv") - else - c.LLVMBuildUDiv(self.builder, lhs, rhs, "udiv"); - self.mapRef(result); - }, - .mod => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types); - const result = if (is_float) - c.LLVMBuildFRem(self.builder, lhs, rhs, "fmod") - else if (isSignedType(instruction.ty)) - c.LLVMBuildSRem(self.builder, lhs, rhs, "srem") - else - c.LLVMBuildURem(self.builder, lhs, rhs, "urem"); - self.mapRef(result); - }, - .neg => |un| { - const operand = self.resolveRef(un.operand); - const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types); - const result = if (is_float) - c.LLVMBuildFNeg(self.builder, operand, "fneg") - else - c.LLVMBuildNeg(self.builder, operand, "neg"); - self.mapRef(result); - }, + .add => |bin| self.ops().emitAdd(instruction, bin), + .sub => |bin| self.ops().emitSub(instruction, bin), + .mul => |bin| self.ops().emitMul(instruction, bin), + .div => |bin| self.ops().emitDiv(instruction, bin), + .mod => |bin| self.ops().emitMod(instruction, bin), + .neg => |un| self.ops().emitNeg(instruction, un), // ── Bitwise ──────────────────────────────────────────── - .bit_and => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - self.mapRef(c.LLVMBuildAnd(self.builder, lhs, rhs, "and")); - }, - .bit_or => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - self.mapRef(c.LLVMBuildOr(self.builder, lhs, rhs, "or")); - }, - .bit_xor => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - self.mapRef(c.LLVMBuildXor(self.builder, lhs, rhs, "xor")); - }, - .bit_not => |un| { - const operand = self.resolveRef(un.operand); - self.mapRef(c.LLVMBuildNot(self.builder, operand, "not")); - }, - .shl => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - self.mapRef(c.LLVMBuildShl(self.builder, lhs, rhs, "shl")); - }, - .shr => |bin| { - var lhs = self.resolveRef(bin.lhs); - var rhs = self.resolveRef(bin.rhs); - self.matchBinOpTypes(&lhs, &rhs, instruction.ty); - // Use arithmetic shift right for signed, logical for unsigned - const result = if (isSignedType(instruction.ty)) - c.LLVMBuildAShr(self.builder, lhs, rhs, "ashr") - else - c.LLVMBuildLShr(self.builder, lhs, rhs, "lshr"); - self.mapRef(result); - }, + .bit_and => |bin| self.ops().emitBitAnd(instruction, bin), + .bit_or => |bin| self.ops().emitBitOr(instruction, bin), + .bit_xor => |bin| self.ops().emitBitXor(instruction, bin), + .bit_not => |un| self.ops().emitBitNot(un), + .shl => |bin| self.ops().emitShl(instruction, bin), + .shr => |bin| self.ops().emitShr(instruction, bin), // ── Comparisons ─────────────────────────────────────── - .cmp_eq => |bin| self.emitCmp(bin, instruction.ty, c.LLVMIntEQ, c.LLVMRealOEQ), - .cmp_ne => |bin| self.emitCmp(bin, instruction.ty, c.LLVMIntNE, c.LLVMRealONE), - .cmp_lt => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLT, c.LLVMIntULT, c.LLVMRealOLT), - .cmp_le => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLE, c.LLVMIntULE, c.LLVMRealOLE), - .cmp_gt => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGT, c.LLVMIntUGT, c.LLVMRealOGT), - .cmp_ge => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGE, c.LLVMIntUGE, c.LLVMRealOGE), - .str_eq => |bin| self.emitStrCmp(bin, true), - .str_ne => |bin| self.emitStrCmp(bin, false), + .cmp_eq => |bin| self.ops().emitCmpEq(instruction, bin), + .cmp_ne => |bin| self.ops().emitCmpNe(instruction, bin), + .cmp_lt => |bin| self.ops().emitCmpLt(instruction, bin), + .cmp_le => |bin| self.ops().emitCmpLe(instruction, bin), + .cmp_gt => |bin| self.ops().emitCmpGt(instruction, bin), + .cmp_ge => |bin| self.ops().emitCmpGe(instruction, bin), + .str_eq => |bin| self.ops().emitStrEq(bin), + .str_ne => |bin| self.ops().emitStrNe(bin), // ── Logical ─────────────────────────────────────────── - .bool_and => |bin| { - const lhs = self.resolveRef(bin.lhs); - const rhs = self.resolveRef(bin.rhs); - self.mapRef(c.LLVMBuildAnd(self.builder, lhs, rhs, "land")); - }, - .bool_or => |bin| { - const lhs = self.resolveRef(bin.lhs); - const rhs = self.resolveRef(bin.rhs); - self.mapRef(c.LLVMBuildOr(self.builder, lhs, rhs, "lor")); - }, - .bool_not => |un| { - const operand = self.resolveRef(un.operand); - self.mapRef(c.LLVMBuildNot(self.builder, operand, "lnot")); - }, + .bool_and => |bin| self.ops().emitBoolAnd(bin), + .bool_or => |bin| self.ops().emitBoolOr(bin), + .bool_not => |un| self.ops().emitBoolNot(un), // ── Memory ──────────────────────────────────────────── .alloca => |elem_ty| { @@ -3153,16 +2978,16 @@ pub const LLVMEmitter = struct { // ── Ref tracking ──────────────────────────────────────────────── - fn mapRef(self: *LLVMEmitter, val: c.LLVMValueRef) void { + pub fn mapRef(self: *LLVMEmitter, val: c.LLVMValueRef) void { self.ref_map.put(self.ref_counter, val) catch unreachable; self.ref_counter += 1; } - fn advanceRefCounter(self: *LLVMEmitter) void { + pub fn advanceRefCounter(self: *LLVMEmitter) void { self.ref_counter += 1; } - fn resolveRef(self: *LLVMEmitter, ref: Ref) c.LLVMValueRef { + pub fn resolveRef(self: *LLVMEmitter, ref: Ref) c.LLVMValueRef { if (ref.isNone()) { return c.LLVMGetUndef(self.cached_i64); } @@ -3276,7 +3101,7 @@ pub const LLVMEmitter = struct { // ── Comparison helpers ──────────────────────────────────────────── - fn emitCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, int_pred: c_uint, float_pred: c_uint) void { + pub fn emitCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, int_pred: c_uint, float_pred: c_uint) void { var lhs = self.resolveRef(bin.lhs); var rhs = self.resolveRef(bin.rhs); // Determine if float by inspecting operand LLVM type @@ -3358,7 +3183,7 @@ pub const LLVMEmitter = struct { self.mapRef(result); } - fn emitCmpOrdered(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, signed_pred: c_uint, unsigned_pred: c_uint, float_pred: c_uint) void { + pub fn emitCmpOrdered(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, signed_pred: c_uint, unsigned_pred: c_uint, float_pred: c_uint) void { var lhs = self.resolveRef(bin.lhs); var rhs = self.resolveRef(bin.rhs); const lhs_ty = c.LLVMTypeOf(lhs); @@ -3391,7 +3216,7 @@ pub const LLVMEmitter = struct { } /// String comparison via memcmp: compare length first, then content. - fn emitStrCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, is_eq: bool) void { + pub fn emitStrCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, is_eq: bool) void { const lhs = self.resolveRef(bin.lhs); const rhs = self.resolveRef(bin.rhs); const b = self.builder; @@ -3905,7 +3730,7 @@ pub const LLVMEmitter = struct { /// Coerce both binary operands to match the instruction's result type. /// E.g. if result is i64 but one operand is i32, sext it. - fn matchBinOpTypes(self: *LLVMEmitter, lhs: *c.LLVMValueRef, rhs: *c.LLVMValueRef, result_ty: TypeId) void { + pub fn matchBinOpTypes(self: *LLVMEmitter, lhs: *c.LLVMValueRef, rhs: *c.LLVMValueRef, result_ty: TypeId) void { const target = self.toLLVMType(result_ty); lhs.* = self.coerceArg(lhs.*, target); rhs.* = self.coerceArg(rhs.*, target); @@ -3925,7 +3750,7 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } - fn reflection(self: *LLVMEmitter) llvm_reflection.Reflection { + pub fn reflection(self: *LLVMEmitter) llvm_reflection.Reflection { return .{ .e = self }; } @@ -3933,6 +3758,10 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } + fn ops(self: *LLVMEmitter) llvm_ops.Ops { + return .{ .e = self }; + } + /// IR-type → LLVM-type lowering lives in `backend/llvm/types.zig` /// (`TypeLowering`). This stays the facade entry point (~97 callers). pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { @@ -4057,7 +3886,7 @@ pub const LLVMEmitter = struct { return c.LLVMConstArray(elem_ty, vals.ptr, n); } - fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef { + pub fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef { // LLVMBuildGlobalStringPtr needs a null-terminated C string const str_z = self.alloc.dupeZ(u8, str) catch unreachable; defer self.alloc.free(str_z); @@ -4393,7 +4222,7 @@ fn isFloatType(ty: TypeId) bool { } /// Check if a TypeId is a float type, including float vectors. -fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool { +pub fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool { if (ty == .f32 or ty == .f64) return true; if (!ty.isBuiltin()) { const info = types.get(ty); @@ -4402,7 +4231,7 @@ fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool { return false; } -fn isSignedType(ty: TypeId) bool { +pub fn isSignedType(ty: TypeId) bool { return switch (ty) { .s8, .s16, .s32, .s64, .isize => true, else => false, From b4faefa607df05891d383e3001bda0db21bbb97c Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 11:26:31 +0300 Subject: [PATCH 11/26] refactor(backend): move memory/globals/conversion/pointer handlers into ops.zig (A7.4 slice b) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relocate the `// ── Memory ──`, `// ── Globals ──`, `// ── Conversions ──`, and `// ── Pointer ops ──` opcode handler bodies out of `emitInst` in src/ir/emit_llvm.zig into the existing `Ops` facade in src/backend/llvm/ops.zig. Each `emitInst` arm now delegates via `self.ops().emit(...)`. Widen `emitConversion`, `coerceArg`, and `getRefIRType` to `pub` (the only helpers the moved bodies call). Pure relocation: zero snapshot churn. --- src/backend/llvm/ops.zig | 176 +++++++++++++++++++++++++++++++++++++-- src/ir/emit_llvm.zig | 160 ++++------------------------------- 2 files changed, 187 insertions(+), 149 deletions(-) diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index 1f627e2..36f66bb 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -9,16 +9,22 @@ const LLVMEmitter = emit.LLVMEmitter; const Inst = ir_inst.Inst; const BinOp = ir_inst.BinOp; const UnaryOp = ir_inst.UnaryOp; +const Store = ir_inst.Store; +const Conversion = ir_inst.Conversion; +const GlobalId = ir_inst.GlobalId; +const GlobalSet = ir_inst.GlobalSet; +const FuncId = ir_inst.FuncId; const TypeId = ir_types.TypeId; const StringId = ir_types.StringId; -/// Scalar instruction-emission handlers for `emitInst`: the constant, -/// arithmetic, bitwise, comparison, and logical opcodes. A backend -/// `*LLVMEmitter` facade (field `e`): each method emits one opcode's LLVM IR via -/// `self.e.*`. The shared infra these bodies call back into +/// Instruction-emission handlers for `emitInst`: the constant, arithmetic, +/// bitwise, comparison, logical, memory, globals, conversion, and pointer +/// opcodes. A backend `*LLVMEmitter` facade (field `e`): each method emits one +/// opcode's LLVM IR via `self.e.*`. The shared infra these bodies call back into /// (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/ -/// `emitStrCmp`/`emitStringConstant`/`reflection`) stays on `LLVMEmitter`. -/// `emitInst`'s scalar arms reach these via `self.ops()`. +/// `emitStrCmp`/`emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/ +/// `getRefIRType`) stays on `LLVMEmitter`. `emitInst`'s arms reach these via +/// `self.ops()`. pub const Ops = struct { e: *LLVMEmitter, @@ -286,4 +292,162 @@ pub const Ops = struct { const operand = self.e.resolveRef(un.operand); self.e.mapRef(c.LLVMBuildNot(self.e.builder, operand, "lnot")); } + + // ── Memory ──────────────────────────────────────────── + pub fn emitAlloca(self: Ops, elem_ty: TypeId) void { + const llvm_ty = self.e.toLLVMType(elem_ty); + const result = c.LLVMBuildAlloca(self.e.builder, llvm_ty, "alloca"); + self.e.mapRef(result); + } + + pub fn emitLoad(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const ptr = self.e.resolveRef(un.operand); + const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr)); + if (ptr_kind == c.LLVMPointerTypeKind and instruction.ty != .void) { + const llvm_ty = self.e.toLLVMType(instruction.ty); + const result = c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "load"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(if (instruction.ty == .void) .s64 else instruction.ty))); + } + } + + pub fn emitStore(self: Ops, st: Store) void { + const ptr = self.e.resolveRef(st.ptr); + var val = self.e.resolveRef(st.val); + // Guard: don't store void types or store to non-pointer + const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr)); + const val_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(val)); + if (ptr_kind == c.LLVMPointerTypeKind and val_kind != c.LLVMVoidTypeKind) { + // Coerce value to match the IR-declared pointer target type. + // E.g. storing i64 to *i8 (from index_gep on string) needs truncation. + // + // Only unwrap .pointer (from index_gep/alloca: *element → element). + // Never unwrap .many_pointer — it only appears as struct_gep field + // value types (e.g., [*]BigNode), where unwrapping to the element + // type gives a wrong store size (stores BigNode-sized instead of ptr). + if (self.e.getRefIRType(st.ptr)) |ptr_ir_ty| { + const pointee_info = self.e.ir_mod.types.get(ptr_ir_ty); + const target_ty: ?c.LLVMTypeRef = switch (pointee_info) { + .pointer => |p| self.e.toLLVMType(p.pointee), + else => null, + }; + if (target_ty) |tt| { + val = self.e.coerceArg(val, tt); + } + } + _ = c.LLVMBuildStore(self.e.builder, val, ptr); + } + self.e.advanceRefCounter(); + } + + // ── Globals ─────────────────────────────────────────── + pub fn emitGlobalGet(self: Ops, instruction: *const Inst, gid: GlobalId) void { + const llvm_global = self.e.global_map.get(gid.index()) orelse { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + }; + const llvm_ty = self.e.toLLVMType(instruction.ty); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, llvm_ty, llvm_global, "gload")); + } + + pub fn emitGlobalAddr(self: Ops, gid: GlobalId) void { + const llvm_global = self.e.global_map.get(gid.index()) orelse { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + return; + }; + // Return the global's address directly (no load) + self.e.mapRef(llvm_global); + } + + pub fn emitFuncRef(self: Ops, fid: FuncId) void { + // Produce a reference to the function as a function pointer value + if (self.e.func_map.get(@intFromEnum(fid))) |llvm_func| { + self.e.mapRef(llvm_func); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + pub fn emitGlobalSet(self: Ops, gs: GlobalSet) void { + const llvm_global = self.e.global_map.get(gs.global.index()) orelse { + self.e.advanceRefCounter(); + return; + }; + const val = self.e.resolveRef(gs.value); + _ = c.LLVMBuildStore(self.e.builder, val, llvm_global); + self.e.advanceRefCounter(); + } + + // ── Conversions ─────────────────────────────────────── + pub fn emitWiden(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = self.e.emitConversion(operand, conv.from, conv.to, to_ty); + self.e.mapRef(result); + } + + pub fn emitNarrow(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = self.e.emitConversion(operand, conv.from, conv.to, to_ty); + self.e.mapRef(result); + } + + pub fn emitBitcast(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + // LLVMBuildBitCast doesn't accept ptr↔int on modern + // LLVM. Dispatch to PtrToInt / IntToPtr when needed — + // lower.zig emits a `bitcast` IR op for both shapes. + const from_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(operand)); + const to_kind = c.LLVMGetTypeKind(to_ty); + if (from_kind == c.LLVMPointerTypeKind and to_kind == c.LLVMIntegerTypeKind) { + const i64_val = c.LLVMBuildPtrToInt(self.e.builder, operand, self.e.cached_i64, "pti"); + const w = c.LLVMGetIntTypeWidth(to_ty); + if (w == 64) { + self.e.mapRef(i64_val); + } else if (w < 64) { + self.e.mapRef(c.LLVMBuildTrunc(self.e.builder, i64_val, to_ty, "pti.tr")); + } else { + self.e.mapRef(c.LLVMBuildZExt(self.e.builder, i64_val, to_ty, "pti.ext")); + } + } else if (from_kind == c.LLVMIntegerTypeKind and to_kind == c.LLVMPointerTypeKind) { + self.e.mapRef(c.LLVMBuildIntToPtr(self.e.builder, operand, to_ty, "itp")); + } else { + self.e.mapRef(c.LLVMBuildBitCast(self.e.builder, operand, to_ty, "bitcast")); + } + } + + pub fn emitIntToFloat(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = if (emit.isSignedType(conv.from)) + c.LLVMBuildSIToFP(self.e.builder, operand, to_ty, "sitofp") + else + c.LLVMBuildUIToFP(self.e.builder, operand, to_ty, "uitofp"); + self.e.mapRef(result); + } + + pub fn emitFloatToInt(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = if (emit.isSignedType(conv.to)) + c.LLVMBuildFPToSI(self.e.builder, operand, to_ty, "fptosi") + else + c.LLVMBuildFPToUI(self.e.builder, operand, to_ty, "fptoui"); + self.e.mapRef(result); + } + + // ── Pointer ops ─────────────────────────────────────── + pub fn emitAddrOf(self: Ops, un: UnaryOp) void { + // addr_of returns the pointer directly (the operand is already a ptr from alloca) + self.e.mapRef(self.e.resolveRef(un.operand)); + } + + pub fn emitDeref(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const ptr = self.e.resolveRef(un.operand); + const llvm_ty = self.e.toLLVMType(instruction.ty); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "deref")); + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 4c547d1..037d396 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1390,151 +1390,25 @@ pub const LLVMEmitter = struct { .bool_not => |un| self.ops().emitBoolNot(un), // ── Memory ──────────────────────────────────────────── - .alloca => |elem_ty| { - const llvm_ty = self.toLLVMType(elem_ty); - const result = c.LLVMBuildAlloca(self.builder, llvm_ty, "alloca"); - self.mapRef(result); - }, - .load => |un| { - const ptr = self.resolveRef(un.operand); - const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr)); - if (ptr_kind == c.LLVMPointerTypeKind and instruction.ty != .void) { - const llvm_ty = self.toLLVMType(instruction.ty); - const result = c.LLVMBuildLoad2(self.builder, llvm_ty, ptr, "load"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(if (instruction.ty == .void) .s64 else instruction.ty))); - } - }, - .store => |st| { - const ptr = self.resolveRef(st.ptr); - var val = self.resolveRef(st.val); - // Guard: don't store void types or store to non-pointer - const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr)); - const val_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(val)); - if (ptr_kind == c.LLVMPointerTypeKind and val_kind != c.LLVMVoidTypeKind) { - // Coerce value to match the IR-declared pointer target type. - // E.g. storing i64 to *i8 (from index_gep on string) needs truncation. - // - // Only unwrap .pointer (from index_gep/alloca: *element → element). - // Never unwrap .many_pointer — it only appears as struct_gep field - // value types (e.g., [*]BigNode), where unwrapping to the element - // type gives a wrong store size (stores BigNode-sized instead of ptr). - if (self.getRefIRType(st.ptr)) |ptr_ir_ty| { - const pointee_info = self.ir_mod.types.get(ptr_ir_ty); - const target_ty: ?c.LLVMTypeRef = switch (pointee_info) { - .pointer => |p| self.toLLVMType(p.pointee), - else => null, - }; - if (target_ty) |tt| { - val = self.coerceArg(val, tt); - } - } - _ = c.LLVMBuildStore(self.builder, val, ptr); - } - self.advanceRefCounter(); - }, + .alloca => |elem_ty| self.ops().emitAlloca(elem_ty), + .load => |un| self.ops().emitLoad(instruction, un), + .store => |st| self.ops().emitStore(st), // ── Globals ─────────────────────────────────────────── - .global_get => |gid| { - const llvm_global = self.global_map.get(gid.index()) orelse { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - }; - const llvm_ty = self.toLLVMType(instruction.ty); - self.mapRef(c.LLVMBuildLoad2(self.builder, llvm_ty, llvm_global, "gload")); - }, - .global_addr => |gid| { - const llvm_global = self.global_map.get(gid.index()) orelse { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - return; - }; - // Return the global's address directly (no load) - self.mapRef(llvm_global); - }, - .func_ref => |fid| { - // Produce a reference to the function as a function pointer value - if (self.func_map.get(@intFromEnum(fid))) |llvm_func| { - self.mapRef(llvm_func); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, - .global_set => |gs| { - const llvm_global = self.global_map.get(gs.global.index()) orelse { - self.advanceRefCounter(); - return; - }; - const val = self.resolveRef(gs.value); - _ = c.LLVMBuildStore(self.builder, val, llvm_global); - self.advanceRefCounter(); - }, + .global_get => |gid| self.ops().emitGlobalGet(instruction, gid), + .global_addr => |gid| self.ops().emitGlobalAddr(gid), + .func_ref => |fid| self.ops().emitFuncRef(fid), + .global_set => |gs| self.ops().emitGlobalSet(gs), // ── Conversions ─────────────────────────────────────── - .widen => |conv| { - const operand = self.resolveRef(conv.operand); - const to_ty = self.toLLVMType(conv.to); - const result = self.emitConversion(operand, conv.from, conv.to, to_ty); - self.mapRef(result); - }, - .narrow => |conv| { - const operand = self.resolveRef(conv.operand); - const to_ty = self.toLLVMType(conv.to); - const result = self.emitConversion(operand, conv.from, conv.to, to_ty); - self.mapRef(result); - }, - .bitcast => |conv| { - const operand = self.resolveRef(conv.operand); - const to_ty = self.toLLVMType(conv.to); - // LLVMBuildBitCast doesn't accept ptr↔int on modern - // LLVM. Dispatch to PtrToInt / IntToPtr when needed — - // lower.zig emits a `bitcast` IR op for both shapes. - const from_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(operand)); - const to_kind = c.LLVMGetTypeKind(to_ty); - if (from_kind == c.LLVMPointerTypeKind and to_kind == c.LLVMIntegerTypeKind) { - const i64_val = c.LLVMBuildPtrToInt(self.builder, operand, self.cached_i64, "pti"); - const w = c.LLVMGetIntTypeWidth(to_ty); - if (w == 64) { - self.mapRef(i64_val); - } else if (w < 64) { - self.mapRef(c.LLVMBuildTrunc(self.builder, i64_val, to_ty, "pti.tr")); - } else { - self.mapRef(c.LLVMBuildZExt(self.builder, i64_val, to_ty, "pti.ext")); - } - } else if (from_kind == c.LLVMIntegerTypeKind and to_kind == c.LLVMPointerTypeKind) { - self.mapRef(c.LLVMBuildIntToPtr(self.builder, operand, to_ty, "itp")); - } else { - self.mapRef(c.LLVMBuildBitCast(self.builder, operand, to_ty, "bitcast")); - } - }, - .int_to_float => |conv| { - const operand = self.resolveRef(conv.operand); - const to_ty = self.toLLVMType(conv.to); - const result = if (isSignedType(conv.from)) - c.LLVMBuildSIToFP(self.builder, operand, to_ty, "sitofp") - else - c.LLVMBuildUIToFP(self.builder, operand, to_ty, "uitofp"); - self.mapRef(result); - }, - .float_to_int => |conv| { - const operand = self.resolveRef(conv.operand); - const to_ty = self.toLLVMType(conv.to); - const result = if (isSignedType(conv.to)) - c.LLVMBuildFPToSI(self.builder, operand, to_ty, "fptosi") - else - c.LLVMBuildFPToUI(self.builder, operand, to_ty, "fptoui"); - self.mapRef(result); - }, + .widen => |conv| self.ops().emitWiden(conv), + .narrow => |conv| self.ops().emitNarrow(conv), + .bitcast => |conv| self.ops().emitBitcast(conv), + .int_to_float => |conv| self.ops().emitIntToFloat(conv), + .float_to_int => |conv| self.ops().emitFloatToInt(conv), // ── Pointer ops ─────────────────────────────────────── - .addr_of => |un| { - // addr_of returns the pointer directly (the operand is already a ptr from alloca) - self.mapRef(self.resolveRef(un.operand)); - }, - .deref => |un| { - const ptr = self.resolveRef(un.operand); - const llvm_ty = self.toLLVMType(instruction.ty); - self.mapRef(c.LLVMBuildLoad2(self.builder, llvm_ty, ptr, "deref")); - }, + .addr_of => |un| self.ops().emitAddrOf(un), + .deref => |un| self.ops().emitDeref(instruction, un), // ── Calls ───────────────────────────────────────────── .objc_msg_send => |msg| { @@ -3273,7 +3147,7 @@ pub const LLVMEmitter = struct { // ── Conversion helpers ────────────────────────────────────────── - fn emitConversion(self: *LLVMEmitter, operand: c.LLVMValueRef, from: TypeId, to: TypeId, to_ty: c.LLVMTypeRef) c.LLVMValueRef { + pub fn emitConversion(self: *LLVMEmitter, operand: c.LLVMValueRef, from: TypeId, to: TypeId, to_ty: c.LLVMTypeRef) c.LLVMValueRef { const from_float = isFloatOrVecFloat(from, &self.ir_mod.types); const to_float = isFloatOrVecFloat(to, &self.ir_mod.types); @@ -3588,7 +3462,7 @@ pub const LLVMEmitter = struct { /// Coerce a call argument to match the expected parameter type. /// Handles int width mismatches (trunc/ext), float width, and int↔float. - fn coerceArg(self: *LLVMEmitter, val: c.LLVMValueRef, param_ty: c.LLVMTypeRef) c.LLVMValueRef { + pub fn coerceArg(self: *LLVMEmitter, val: c.LLVMValueRef, param_ty: c.LLVMTypeRef) c.LLVMValueRef { const val_ty = c.LLVMTypeOf(val); if (val_ty == param_ty) return val; const val_kind = c.LLVMGetTypeKind(val_ty); @@ -3714,7 +3588,7 @@ pub const LLVMEmitter = struct { } /// Look up the IR type of a Ref in the current function (for store coercion). - fn getRefIRType(self: *LLVMEmitter, ref: Ref) ?TypeId { + pub fn getRefIRType(self: *LLVMEmitter, ref: Ref) ?TypeId { const func = &self.ir_mod.functions.items[self.current_func_idx]; const idx = ref.index(); // Check if it's a function param (refs 0..N-1) From 5388895b3e1e0e950b1546e5d8ad3eb40e15e3ac Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 11:45:30 +0300 Subject: [PATCH 12/26] refactor(backend): move call + call-extension handlers into ops.zig (A7.4 slice c) Relocate the Calls (objc_msg_send / jni_msg_send / call / call_indirect) and Call-extensions (call_builtin / compiler_call / call_closure) emitInst handler groups out of emit_llvm.zig into the existing Ops facade. Each emitInst arm now delegates via self.ops().emit(...). Behavior-preserving pure relocation; emitted LLVM IR is byte-identical (361/0 examples, no snapshot churn). Shared call infra stays on LLVMEmitter, widened pub only as the moved bodies require: extractSlicePtr, loadJniFn, getObjcMsgSendValue, the math F32/F64 declarators + types, getOrDeclareWrite/getWriteType, ffiCtors, materializeByvalArg, emitCStringGlobal, emitJniConstructor, and the Jni slot-offset constants. emitJniConstructor remains in emit_llvm.zig (A7.3 decision); the moved jni arm calls it via self.e.emitJniConstructor(...). --- src/backend/llvm/ops.zig | 708 +++++++++++++++++++++++++++++++++++- src/ir/emit_llvm.zig | 765 +++------------------------------------ 2 files changed, 749 insertions(+), 724 deletions(-) diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index 36f66bb..f5174bc 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -4,8 +4,10 @@ const c = llvm.c; const emit = @import("../../ir/emit_llvm.zig"); const ir_inst = @import("../../ir/inst.zig"); const ir_types = @import("../../ir/types.zig"); +const interp_mod = @import("../../ir/interp.zig"); const LLVMEmitter = emit.LLVMEmitter; +const Interpreter = interp_mod.Interpreter; const Inst = ir_inst.Inst; const BinOp = ir_inst.BinOp; const UnaryOp = ir_inst.UnaryOp; @@ -14,17 +16,24 @@ const Conversion = ir_inst.Conversion; const GlobalId = ir_inst.GlobalId; const GlobalSet = ir_inst.GlobalSet; const FuncId = ir_inst.FuncId; +const Call = ir_inst.Call; +const CallIndirect = ir_inst.CallIndirect; +const ObjcMsgSend = ir_inst.ObjcMsgSend; +const JniMsgSend = ir_inst.JniMsgSend; +const BuiltinCall = ir_inst.BuiltinCall; const TypeId = ir_types.TypeId; const StringId = ir_types.StringId; /// Instruction-emission handlers for `emitInst`: the constant, arithmetic, -/// bitwise, comparison, logical, memory, globals, conversion, and pointer -/// opcodes. A backend `*LLVMEmitter` facade (field `e`): each method emits one -/// opcode's LLVM IR via `self.e.*`. The shared infra these bodies call back into -/// (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/ -/// `emitStrCmp`/`emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/ -/// `getRefIRType`) stays on `LLVMEmitter`. `emitInst`'s arms reach these via -/// `self.ops()`. +/// bitwise, comparison, logical, memory, globals, conversion, pointer, and +/// call opcodes (direct/indirect/objc/jni dispatch plus builtin, compiler, +/// and closure calls). A backend `*LLVMEmitter` facade (field `e`): each +/// method emits one opcode's LLVM IR via `self.e.*`. The shared infra these +/// bodies call back into (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/ +/// `emitCmpOrdered`/`emitStrCmp`/`emitStringConstant`/`reflection`/ +/// `emitConversion`/`coerceArg`/`getRefIRType`/`loadJniFn`/`extractSlicePtr`/ +/// `emitJniConstructor`) stays on `LLVMEmitter`. `emitInst`'s arms reach these +/// via `self.ops()`. pub const Ops = struct { e: *LLVMEmitter, @@ -450,4 +459,689 @@ pub const Ops = struct { const llvm_ty = self.e.toLLVMType(instruction.ty); self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "deref")); } + + // ── Calls ───────────────────────────────────────────── + pub fn emitObjcMsgSend(self: Ops, instruction: *const Inst, msg: ObjcMsgSend) void { + const msg_send = self.e.getObjcMsgSendValue(); + // Detect the sret case: >16 B non-HFA struct return. + // Same predicate as the plain-foreign-call path so the + // two arms stay in lockstep. + const raw_ret_ty = self.e.toLLVMType(instruction.ty); + const uses_sret = self.e.needsByval(instruction.ty, raw_ret_ty); + const ret_ty = if (uses_sret) self.e.cached_void else raw_ret_ty; + + // Slot layout: + // uses_sret = false → [recv, sel, args...] + // uses_sret = true → [sret_slot, recv, sel, args...] + const sret_off: usize = if (uses_sret) 1 else 0; + const total_params: usize = 2 + msg.args.len + sret_off; + const param_types = self.e.alloc.alloc(c.LLVMTypeRef, total_params) catch unreachable; + defer self.e.alloc.free(param_types); + const call_args = self.e.alloc.alloc(c.LLVMValueRef, total_params) catch unreachable; + defer self.e.alloc.free(call_args); + + var sret_slot: c.LLVMValueRef = null; + if (uses_sret) { + sret_slot = c.LLVMBuildAlloca(self.e.builder, raw_ret_ty, "objc.sret"); + param_types[0] = self.e.cached_ptr; + call_args[0] = sret_slot; + } + + // recv (typed *void from the IR) + param_types[sret_off] = self.e.cached_ptr; + call_args[sret_off] = self.e.coerceArg(self.e.resolveRef(msg.recv), self.e.cached_ptr); + // sel (loaded SEL — opaque ptr) + param_types[sret_off + 1] = self.e.cached_ptr; + call_args[sret_off + 1] = self.e.coerceArg(self.e.resolveRef(msg.sel), self.e.cached_ptr); + // additional args take their IR types, with ABI + // coercion applied so structs / strings decay the + // same way they do for any C foreign call. + for (msg.args, 0..) |arg_ref, i| { + const raw_ty = self.e.getRefIRType(arg_ref) orelse .void; + const raw_llvm = self.e.toLLVMType(raw_ty); + const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm); + param_types[i + 2 + sret_off] = coerced_ty; + call_args[i + 2 + sret_off] = self.e.coerceArg(self.e.resolveRef(arg_ref), coerced_ty); + } + + const fn_ty = c.LLVMFunctionType(ret_ty, param_types.ptr, @intCast(total_params), 0); + const call_label: [*:0]const u8 = if (instruction.ty == .void or uses_sret) "" else "objc.msg"; + var result = c.LLVMBuildCall2(self.e.builder, fn_ty, msg_send, call_args.ptr, @intCast(total_params), call_label); + if (uses_sret) { + // Tag the call's arg 0 (sret slot) with the sret + // attribute so the AArch64 / SysV backends route + // through the x8 / hidden-pointer convention. + const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4); + const sret_attr = c.LLVMCreateTypeAttribute(self.e.context, sret_kind, raw_ret_ty); + const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1)); + c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr); + result = c.LLVMBuildLoad2(self.e.builder, raw_ret_ty, sret_slot, "objc.sret.load"); + } + // Always mapRef — the IR Ref counter for this + // instruction advances regardless of return type, + // so skipping it would misalign every subsequent + // ref lookup in this function. + self.e.mapRef(result); + } + + pub fn emitJniMsgSend(self: Ops, instruction: *const Inst, msg: JniMsgSend) void { + // JNI vtable indirection: + // ifs = *env // JNINativeInterface* + // instance: cls = ifs[GetObjectClass](env, target) + // mid = ifs[GetMethodID](env, cls, name, sig) + // ifs[CallMethod](env, target, mid, args...) + // static: target IS the jclass — skip GetObjectClass + // mid = ifs[GetStaticMethodID](env, target, name, sig) + // ifs[CallStaticMethod](env, target, mid, args...) + // ctor: cls = ifs[FindClass](env, parent_class_path) + // mid = ifs[GetMethodID](env, cls, "", sig) + // ifs[NewObject](env, cls, mid, args...) → jobject + // nonvirt: handled below via FindClass + GetMethodID + + // CallNonvirtualMethod. + // The cached path (msg.cache_key != null) still shares one + // (jclass GlobalRef, jmethodID) pair per literal (name, sig). + if (msg.is_constructor) { + self.e.emitJniConstructor(msg, instruction.ty); + return; + } + const ret_ty_id = instruction.ty; + const is_pointer_ret = switch (self.e.ir_mod.types.get(ret_ty_id)) { + .pointer, .many_pointer => true, + else => false, + }; + const call_method_offset: u32 = if (msg.is_static) blk: { + if (is_pointer_ret) break :blk emit.Jni.CallStaticObjectMethod; + break :blk switch (ret_ty_id) { + .void => emit.Jni.CallStaticVoidMethod, + .s32 => emit.Jni.CallStaticIntMethod, + .s64 => emit.Jni.CallStaticLongMethod, + .f32 => emit.Jni.CallStaticFloatMethod, + .f64 => emit.Jni.CallStaticDoubleMethod, + .bool => emit.Jni.CallStaticBooleanMethod, + else => { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + }, + }; + } else if (msg.is_nonvirtual) blk: { + if (is_pointer_ret) break :blk emit.Jni.CallNonvirtualObjectMethod; + break :blk switch (ret_ty_id) { + .void => emit.Jni.CallNonvirtualVoidMethod, + .s32 => emit.Jni.CallNonvirtualIntMethod, + .s64 => emit.Jni.CallNonvirtualLongMethod, + .f32 => emit.Jni.CallNonvirtualFloatMethod, + .f64 => emit.Jni.CallNonvirtualDoubleMethod, + .bool => emit.Jni.CallNonvirtualBooleanMethod, + else => { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + }, + }; + } else blk: { + if (is_pointer_ret) break :blk emit.Jni.CallObjectMethod; + break :blk switch (ret_ty_id) { + .void => emit.Jni.CallVoidMethod, + .s32 => emit.Jni.CallIntMethod, + .s64 => emit.Jni.CallLongMethod, + .f32 => emit.Jni.CallFloatMethod, + .f64 => emit.Jni.CallDoubleMethod, + .bool => emit.Jni.CallBooleanMethod, + else => { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + }, + }; + }; + const get_mid_offset: u32 = if (msg.is_static) emit.Jni.GetStaticMethodID else emit.Jni.GetMethodID; + + const env = self.e.resolveRef(msg.env); + const target = self.e.resolveRef(msg.target); + // String literals lower as `{ptr, i64}` slices in sx IR; + // JNI's `GetMethodID` expects raw C strings, so extract + // field 0 when the source is a slice. + const name_ptr = self.e.extractSlicePtr(self.e.resolveRef(msg.name)); + const sig_ptr = self.e.extractSlicePtr(self.e.resolveRef(msg.sig)); + + const ifs = c.LLVMBuildLoad2(self.e.builder, self.e.cached_ptr, env, "jni.ifs"); + + // Method-ID resolution. When `name` and `sig` are both + // string literals the call site participates in + // `(name, sig)` slot interning (step 1.17): a shared + // pair of static globals holds the `jclass` GlobalRef + // and the `jmethodID`, populated lazily on the first + // call to any matching site. Non-literal sites fall + // back to the per-call `GetObjectClass + GetMethodID` + // sequence (1.15 shape). + const mid = if (msg.cache_key) |ck| blk: { + const pair = self.e.ffiCtors().getOrCreateJniSlots(ck.name_str, ck.sig_str); + const cached_mid = c.LLVMBuildLoad2(self.e.builder, self.e.cached_ptr, pair.mid_slot, "jni.cached.mid"); + const is_cached = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, cached_mid, c.LLVMConstNull(self.e.cached_ptr), "jni.is.cached"); + + const cur_fn = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder)); + const miss_bb = c.LLVMAppendBasicBlockInContext(self.e.context, cur_fn, "jni.miss"); + const cont_bb = c.LLVMAppendBasicBlockInContext(self.e.context, cur_fn, "jni.cont"); + const before_bb = c.LLVMGetInsertBlock(self.e.builder); + _ = c.LLVMBuildCondBr(self.e.builder, is_cached, cont_bb, miss_bb); + + // Miss path: + // instance: GetObjectClass → NewGlobalRef → GetMethodID + // static: target IS class → NewGlobalRef(target) → GetStaticMethodID + c.LLVMPositionBuilderAtEnd(self.e.builder, miss_bb); + const local_cls = if (msg.is_static) target else inst_cls: { + const get_obj_cls = self.e.loadJniFn(ifs, emit.Jni.GetObjectClass, "jni.GetObjectClass"); + var gocls_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr }; + const gocls_ty = c.LLVMFunctionType(self.e.cached_ptr, &gocls_params, 2, 0); + var gocls_args = [_]c.LLVMValueRef{ env, target }; + break :inst_cls c.LLVMBuildCall2(self.e.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); + }; + const new_global_ref = self.e.loadJniFn(ifs, emit.Jni.NewGlobalRef, "jni.NewGlobalRef"); + var ngref_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr }; + const ngref_ty = c.LLVMFunctionType(self.e.cached_ptr, &ngref_params, 2, 0); + var ngref_args = [_]c.LLVMValueRef{ env, local_cls }; + const global_cls = c.LLVMBuildCall2(self.e.builder, ngref_ty, new_global_ref, &ngref_args, 2, "jni.global.cls"); + _ = c.LLVMBuildStore(self.e.builder, global_cls, pair.cls_slot); + const get_mid = self.e.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID"); + var gmid_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr }; + const gmid_ty = c.LLVMFunctionType(self.e.cached_ptr, &gmid_params, 4, 0); + var gmid_args = [_]c.LLVMValueRef{ env, global_cls, name_ptr, sig_ptr }; + const fresh_mid = c.LLVMBuildCall2(self.e.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.fresh.mid"); + _ = c.LLVMBuildStore(self.e.builder, fresh_mid, pair.mid_slot); + const miss_end_bb = c.LLVMGetInsertBlock(self.e.builder); + _ = c.LLVMBuildBr(self.e.builder, cont_bb); + + // Cont: phi the cached vs fresh mid. + c.LLVMPositionBuilderAtEnd(self.e.builder, cont_bb); + const phi = c.LLVMBuildPhi(self.e.builder, self.e.cached_ptr, "jni.mid"); + var phi_vals = [_]c.LLVMValueRef{ cached_mid, fresh_mid }; + var phi_blocks = [_]c.LLVMBasicBlockRef{ before_bb, miss_end_bb }; + c.LLVMAddIncoming(phi, &phi_vals, &phi_blocks, 2); + break :blk phi; + } else blk: { + const cls = if (msg.is_static) target else if (msg.is_nonvirtual) nonvirt_cls: { + // `super.method(args)`: dispatch is bound to a + // specific class (the parent), not subclass-override. + // Resolve via FindClass(parent_path). No caching yet — + // per-call lookup. The parent path is a NUL-terminated + // C string emitted as a private LLVM global. + const path = msg.parent_class_path orelse ""; + const path_global = self.e.emitCStringGlobal(path, "jni.parent.path"); + const find_class = self.e.loadJniFn(ifs, emit.Jni.FindClass, "jni.FindClass"); + var fc_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr }; + const fc_ty = c.LLVMFunctionType(self.e.cached_ptr, &fc_params, 2, 0); + var fc_args = [_]c.LLVMValueRef{ env, path_global }; + break :nonvirt_cls c.LLVMBuildCall2(self.e.builder, fc_ty, find_class, &fc_args, 2, "jni.parent.cls"); + } else inst_cls: { + const get_obj_cls = self.e.loadJniFn(ifs, emit.Jni.GetObjectClass, "jni.GetObjectClass"); + var gocls_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr }; + const gocls_ty = c.LLVMFunctionType(self.e.cached_ptr, &gocls_params, 2, 0); + var gocls_args = [_]c.LLVMValueRef{ env, target }; + break :inst_cls c.LLVMBuildCall2(self.e.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); + }; + const get_mid = self.e.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID"); + var gmid_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr }; + const gmid_ty = c.LLVMFunctionType(self.e.cached_ptr, &gmid_params, 4, 0); + var gmid_args = [_]c.LLVMValueRef{ env, cls, name_ptr, sig_ptr }; + const mid_val = c.LLVMBuildCall2(self.e.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.mid"); + if (msg.is_nonvirtual) { + // Stash cls in a dummy slot so the call site below + // can pick it up. Easiest path: do the call right + // here and return Ref.none, but we need to keep the + // outer phi shape. Instead, return both via tuple + // through an auxiliary local — simplest is to attach + // `cls` to a per-invocation slot. Use a stack alloca. + const cls_slot = c.LLVMBuildAlloca(self.e.builder, self.e.cached_ptr, "jni.parent.cls.slot"); + _ = c.LLVMBuildStore(self.e.builder, cls, cls_slot); + // Tag the slot pointer onto the phi result via the + // generated metadata: we'll re-extract by re-running + // FindClass — actually simpler: lower nonvirtual on + // the spot below. Drop the implicit `break` here: + const call_fn = self.e.loadJniFn(ifs, call_method_offset, "jni.callfn.nonvirtual"); + const raw_ret = self.e.toLLVMType(ret_ty_id); + const total_call_params_nv: usize = 4 + msg.args.len; + const call_param_types_nv = self.e.alloc.alloc(c.LLVMTypeRef, total_call_params_nv) catch unreachable; + defer self.e.alloc.free(call_param_types_nv); + const call_args_nv = self.e.alloc.alloc(c.LLVMValueRef, total_call_params_nv) catch unreachable; + defer self.e.alloc.free(call_args_nv); + call_param_types_nv[0] = self.e.cached_ptr; + call_param_types_nv[1] = self.e.cached_ptr; + call_param_types_nv[2] = self.e.cached_ptr; + call_param_types_nv[3] = self.e.cached_ptr; + call_args_nv[0] = env; + call_args_nv[1] = target; + call_args_nv[2] = cls; + call_args_nv[3] = mid_val; + for (msg.args, 0..) |arg_ref, i| { + const raw_ty = self.e.getRefIRType(arg_ref) orelse .void; + const raw_llvm = self.e.toLLVMType(raw_ty); + const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm); + call_param_types_nv[i + 4] = coerced_ty; + call_args_nv[i + 4] = self.e.coerceArg(self.e.resolveRef(arg_ref), coerced_ty); + } + const call_fn_ty_nv = c.LLVMFunctionType(raw_ret, call_param_types_nv.ptr, @intCast(total_call_params_nv), 0); + const label_nv: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.nonvirtual.ret"; + const result_nv = c.LLVMBuildCall2(self.e.builder, call_fn_ty_nv, call_fn, call_args_nv.ptr, @intCast(total_call_params_nv), label_nv); + self.e.mapRef(result_nv); + return; + } + break :blk mid_val; + }; + + // CallMethod: (JNIEnv*, jobject, jmethodID, args...) -> RetTy + const call_fn = self.e.loadJniFn(ifs, call_method_offset, "jni.callfn"); + const raw_ret = self.e.toLLVMType(ret_ty_id); + const total_call_params: usize = 3 + msg.args.len; + const call_param_types = self.e.alloc.alloc(c.LLVMTypeRef, total_call_params) catch unreachable; + defer self.e.alloc.free(call_param_types); + const call_args = self.e.alloc.alloc(c.LLVMValueRef, total_call_params) catch unreachable; + defer self.e.alloc.free(call_args); + call_param_types[0] = self.e.cached_ptr; + call_param_types[1] = self.e.cached_ptr; + call_param_types[2] = self.e.cached_ptr; + call_args[0] = env; + call_args[1] = target; + call_args[2] = mid; + for (msg.args, 0..) |arg_ref, i| { + const raw_ty = self.e.getRefIRType(arg_ref) orelse .void; + const raw_llvm = self.e.toLLVMType(raw_ty); + const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm); + call_param_types[i + 3] = coerced_ty; + call_args[i + 3] = self.e.coerceArg(self.e.resolveRef(arg_ref), coerced_ty); + } + const call_fn_ty = c.LLVMFunctionType(raw_ret, call_param_types.ptr, @intCast(total_call_params), 0); + const label: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.ret"; + const result = c.LLVMBuildCall2(self.e.builder, call_fn_ty, call_fn, call_args.ptr, @intCast(total_call_params), label); + self.e.mapRef(result); + } + + pub fn emitCall(self: Ops, instruction: *const Inst, call_op: Call) void { + // Evaluate comptime functions at compile time + const callee_func = &self.e.ir_mod.functions.items[call_op.callee.index()]; + if (callee_func.is_comptime and call_op.args.len == 0) { + var interp_inst = Interpreter.init(self.e.ir_mod, self.e.alloc); + interp_inst.build_config = &self.e.build_config; + if (self.e.import_sources) |sm| interp_inst.setSourceMap(sm); + defer interp_inst.deinit(); + if (interp_inst.call(call_op.callee, &.{})) |result| { + if (result.asInt()) |v| { + self.e.mapRef(c.LLVMConstInt(self.e.toLLVMType(instruction.ty), @bitCast(v), 0)); + return; + } else if (result.asFloat()) |v| { + self.e.mapRef(c.LLVMConstReal(self.e.toLLVMType(instruction.ty), v)); + return; + } else if (result.asBool()) |v| { + self.e.mapRef(c.LLVMConstInt(self.e.toLLVMType(instruction.ty), @intFromBool(v), 0)); + return; + } else if (result == .string) { + self.e.mapRef(self.e.emitStringConstant(result.string)); + return; + } + } else |_| {} + } + const callee = self.e.func_map.get(call_op.callee.index()) orelse { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + }; + const callee_needs_c_abi = callee_func.is_extern or callee_func.call_conv == .c; + const callee_raw_ret = self.e.toLLVMType(callee_func.ret); + const callee_uses_sret = callee_needs_c_abi and self.e.needsByval(callee_func.ret, callee_raw_ret); + + // When the callee uses sret, prepend an alloca for the result. + // Index alignment: actual_args[0] = sret_slot; actual_args[i+1] = sx arg i. + const sret_off: usize = if (callee_uses_sret) 1 else 0; + const total_args = call_op.args.len + sret_off; + const args = self.e.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable; + defer self.e.alloc.free(args); + var sret_slot: c.LLVMValueRef = null; + if (callee_uses_sret) { + sret_slot = c.LLVMBuildAlloca(self.e.builder, callee_raw_ret, "sret.slot"); + args[0] = sret_slot; + } + for (call_op.args, 0..) |arg_ref, j| { + args[j + sret_off] = self.e.resolveRef(arg_ref); + } + const arg_count: c_uint = @intCast(total_args); + + // Get the function type from LLVM and coerce arguments + const fn_ty = c.LLVMGlobalGetValueType(callee); + const param_count = c.LLVMCountParamTypes(fn_ty); + if (param_count > 0) { + const param_types = self.e.alloc.alloc(c.LLVMTypeRef, param_count) catch unreachable; + defer self.e.alloc.free(param_types); + c.LLVMGetParamTypes(fn_ty, param_types.ptr); + for (0..@min(args.len, param_count)) |j| { + // The sret slot is already a properly-typed pointer; skip coercion. + if (callee_uses_sret and j == 0) continue; + const fn_param_idx = j - sret_off; + // Materialize byval args before coercion so we pass a ptr instead of the struct value. + if (callee_needs_c_abi and fn_param_idx < callee_func.params.len) { + const ir_ty = callee_func.params[fn_param_idx].ty; + const raw_struct = self.e.toLLVMType(ir_ty); + if (self.e.needsByval(ir_ty, raw_struct)) { + args[j] = self.e.materializeByvalArg(args[j], raw_struct); + continue; + } + } + args[j] = self.e.coerceArg(args[j], param_types[j]); + } + } + // A `void`/`noreturn` call has no value, so it must stay + // unnamed (LLVM rejects a named void result). + const call_is_void_like = instruction.ty == .void or instruction.ty == .noreturn; + const call_label: [*:0]const u8 = if (call_is_void_like or callee_uses_sret) "" else "call"; + var result = c.LLVMBuildCall2(self.e.builder, fn_ty, callee, args.ptr, arg_count, call_label); + if (callee_uses_sret) { + // Mirror the function-decl `sret()` attribute on the call site so the + // LLVM backend lowers arg 0 via x8 (AAPCS64) / hidden ptr (SysV AMD64). + const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4); + const sret_attr = c.LLVMCreateTypeAttribute(self.e.context, sret_kind, callee_raw_ret); + const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1)); + c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr); + // Load the actual struct value the callee wrote into the slot. + result = c.LLVMBuildLoad2(self.e.builder, callee_raw_ret, sret_slot, "sret.load"); + } else if (!call_is_void_like and callee_func.is_extern) { + // Coerce ABI return value (e.g. i64 / [2 x i64]) back to IR struct type if needed + const expected_ty = self.e.toLLVMType(instruction.ty); + result = self.e.coerceArg(result, expected_ty); + } + self.e.mapRef(result); + } + + pub fn emitCallIndirect(self: Ops, instruction: *const Inst, call_op: CallIndirect) void { + const callee = self.e.resolveRef(call_op.callee); + const arg_count: c_uint = @intCast(call_op.args.len); + const args = self.e.alloc.alloc(c.LLVMValueRef, call_op.args.len) catch unreachable; + defer self.e.alloc.free(args); + for (call_op.args, 0..) |arg_ref, j| { + args[j] = self.e.resolveRef(arg_ref); + } + + // Get callee's IR type to resolve parameter types accurately + const callee_ir_ty = self.e.getRefIRType(call_op.callee); + const fn_params: ?[]const TypeId = if (callee_ir_ty) |cty| blk: { + if (!cty.isBuiltin()) { + const ci = self.e.ir_mod.types.get(cty); + switch (ci) { + .function => |f| break :blk f.params, + .closure => |cl| break :blk cl.params, + else => {}, + } + } + break :blk null; + } else null; + + // Read the fn-pointer type's calling convention. Only `.c` opts + // into the C-ABI byval coercion for >16B aggregate params. + const fp_is_c_abi: bool = if (callee_ir_ty) |cty| blk: { + if (!cty.isBuiltin()) { + const ci = self.e.ir_mod.types.get(cty); + if (ci == .function and ci.function.call_conv == .c) break :blk true; + } + break :blk false; + } else false; + + // Default-conv fn-pointers under implicit-ctx carry a hidden + // `*void` (the implicit __sx_ctx) at LLVM slot 0. The IR fn + // type does not include it, so shift fn_params lookups by 1. + const fp_ctx_slots: usize = if (callee_ir_ty) |cty| blk: { + if (!self.e.ir_mod.has_implicit_ctx) break :blk 0; + if (cty.isBuiltin()) break :blk 0; + const ci = self.e.ir_mod.types.get(cty); + switch (ci) { + .function => |f| break :blk if (f.call_conv == .c) @as(usize, 0) else 1, + else => break :blk 0, + } + } else 0; + + const ret_ty = if (callee_ir_ty) |cty| blk: { + if (!cty.isBuiltin()) { + const ci = self.e.ir_mod.types.get(cty); + switch (ci) { + .function => |f| break :blk self.e.toLLVMType(f.ret), + .closure => |cl| break :blk self.e.toLLVMType(cl.ret), + else => {}, + } + } + break :blk self.e.toLLVMType(instruction.ty); + } else self.e.toLLVMType(instruction.ty); + + const param_tys = self.e.alloc.alloc(c.LLVMTypeRef, call_op.args.len) catch unreachable; + defer self.e.alloc.free(param_tys); + if (fn_params) |fp| { + for (0..call_op.args.len) |j| { + // Slots 0..fp_ctx_slots are the implicit __sx_ctx + // (passed as opaque ptr; not in fp). + if (j < fp_ctx_slots) { + param_tys[j] = self.e.cached_ptr; + args[j] = self.e.coerceArg(args[j], self.e.cached_ptr); + continue; + } + const fp_idx = j - fp_ctx_slots; + if (fp_idx < fp.len) { + const raw_struct = self.e.toLLVMType(fp[fp_idx]); + if (fp_is_c_abi and self.e.needsByval(fp[fp_idx], raw_struct)) { + args[j] = self.e.materializeByvalArg(args[j], raw_struct); + param_tys[j] = self.e.cached_ptr; + continue; + } + var llvm_pty = raw_struct; + // Array params in fn-ptr calls decay to pointers (C ABI) + if (c.LLVMGetTypeKind(llvm_pty) == c.LLVMArrayTypeKind) { + llvm_pty = self.e.cached_ptr; + } + param_tys[j] = llvm_pty; + args[j] = self.e.coerceArg(args[j], llvm_pty); + } else { + param_tys[j] = c.LLVMTypeOf(args[j]); + } + } + } else { + for (args, 0..) |arg, j| { + param_tys[j] = c.LLVMTypeOf(arg); + } + } + const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, arg_count, 0); + const icall_void_like = instruction.ty == .void or instruction.ty == .noreturn; + var result = c.LLVMBuildCall2(self.e.builder, fn_ty, callee, args.ptr, arg_count, if (icall_void_like) "" else "icall"); + + // Coerce call result to instruction's expected type + const expected_ty = self.e.toLLVMType(instruction.ty); + if (!icall_void_like and c.LLVMTypeOf(result) != expected_ty) { + result = self.e.coerceArg(result, expected_ty); + } + self.e.mapRef(result); + } + + // ── Call extensions ─────────────────────────────────────── + pub fn emitCallBuiltin(self: Ops, instruction: *const Inst, bi: BuiltinCall) void { + // Builtins that map to libc functions or LLVM intrinsics + switch (bi.builtin) { + .sqrt, .sin, .cos, .floor => { + const val = self.e.resolveRef(bi.args[0]); + const val_ty = c.LLVMTypeOf(val); + const val_kind = c.LLVMGetTypeKind(val_ty); + if (val_kind == c.LLVMFloatTypeKind) { + const f = self.e.getOrDeclareMathF32(bi.builtin); + var args = [_]c.LLVMValueRef{val}; + self.e.mapRef(c.LLVMBuildCall2(self.e.builder, self.e.getMathF32Type(), f, &args, 1, @tagName(bi.builtin))); + } else { + const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.e.coerceArg(val, self.e.cached_f64) else val; + const f = self.e.getOrDeclareMathF64(bi.builtin); + var args = [_]c.LLVMValueRef{coerced}; + self.e.mapRef(c.LLVMBuildCall2(self.e.builder, self.e.getMathF64Type(), f, &args, 1, @tagName(bi.builtin))); + } + }, + .out => { + // out(str): extract ptr and len from string fat pointer, call write(1, ptr, len) + const str_val = self.e.resolveRef(bi.args[0]); + const raw_ptr = c.LLVMBuildExtractValue(self.e.builder, str_val, 0, "str.ptr"); + const str_len = c.LLVMBuildExtractValue(self.e.builder, str_val, 1, "str.len"); + // On wasm32, count param is i32 (size_t) + const count = if (self.e.target_config.isWasm32()) + c.LLVMBuildTrunc(self.e.builder, str_len, self.e.cached_i32, "len.tr") + else + str_len; + const write_fn = self.e.getOrDeclareWrite(); + var write_args = [_]c.LLVMValueRef{ + c.LLVMConstInt(self.e.cached_i32, 1, 0), // fd = stdout + raw_ptr, + count, + }; + _ = c.LLVMBuildCall2(self.e.builder, self.e.getWriteType(), write_fn, &write_args, 3, ""); + self.e.advanceRefCounter(); + }, + .type_name => { + // Dynamic `type_name(t)` at runtime: extract + // the TypeId from the arg (an Any-boxed Type + // value: tag=`.s64.index()`, value=tid), GEP + // into the compiler-emitted `__sx_type_names` + // global, load the string. The arg's LLVM + // shape is the `{i64, i64}` Any aggregate + // (because the IR-side arg type is `.any` + // when boxed); for unboxed direct call sites + // (the arg IR type is `.s64` from + // `const_type`), the value IS the TypeId + // index directly. + const arg_ref = bi.args[0]; + const arg_val = self.e.resolveRef(arg_ref); + const arg_ir_ty = self.e.getRefIRType(arg_ref) orelse TypeId.s64; + const tid_idx = blk: { + if (arg_ir_ty == .any) { + // Boxed: extract value field. + break :blk c.LLVMBuildExtractValue(self.e.builder, arg_val, 1, "tn.tid"); + } + // Bare i64 (TypeId index). + break :blk arg_val; + }; + const arr_global = self.e.reflection().getOrBuildTypeNameArray(); + const arr_len = self.e.type_name_array_len; + const string_ty = self.e.getStringStructType(); + const arr_ty = c.LLVMArrayType(string_ty, arr_len); + const zero = c.LLVMConstInt(self.e.cached_i64, 0, 0); + var indices = [2]c.LLVMValueRef{ zero, tid_idx }; + const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, arr_ty, arr_global, &indices, 2, "tn.gep"); + const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "tn.load"); + self.e.mapRef(result); + }, + .type_eq => { + // Dynamic `type_eq(a, b)` — both args are + // Type values. Extract TypeId from each Any + // box (or use directly if `.s64`-typed), + // icmp eq. + const a = blk: { + const v = self.e.resolveRef(bi.args[0]); + const ty = self.e.getRefIRType(bi.args[0]) orelse TypeId.s64; + if (ty == .any) break :blk c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.a"); + break :blk v; + }; + const b = blk: { + const v = self.e.resolveRef(bi.args[1]); + const ty = self.e.getRefIRType(bi.args[1]) orelse TypeId.s64; + if (ty == .any) break :blk c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.b"); + break :blk v; + }; + const eq_res = c.LLVMBuildICmp(self.e.builder, c.LLVMIntEQ, a, b, "te.eq"); + self.e.mapRef(eq_res); + }, + .has_impl => { + // Runtime has_impl needs a protocol-map + // snapshot — not wired yet. Silent false for + // now; the lower-time fold via + // `tryConstBoolCondition` covers every + // statically-resolvable call. + self.e.mapRef(c.LLVMConstInt(self.e.cached_i1, 0, 0)); + }, + else => { + // size_of, cast — handled by lowering or codegen glue + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + }, + } + } + + pub fn emitCompilerCall(self: Ops, instruction: *const Inst) void { + // Compiler hooks are comptime-only; if one reaches emission, produce undef + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + + pub fn emitCallClosure(self: Ops, instruction: *const Inst, call_op: CallIndirect) void { + // Closure: { fn_ptr, env }. + // + // ABI (when module.has_implicit_ctx): + // trampoline signature: (__sx_ctx, env, args...) + // call_op.args[0] = __sx_ctx (prepended by lowering) + // call_op.args[1..] = user args + // extracted env_ptr = inserted at LLVM slot 1 + // + // ABI (without implicit_ctx): + // trampoline signature: (env, args...) + // call_op.args = user args (no ctx prepend) + // extracted env_ptr = inserted at LLVM slot 0 + const closure = self.e.resolveRef(call_op.callee); + const cl_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(closure)); + if (cl_kind != c.LLVMStructTypeKind) { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + } + const fn_ptr = c.LLVMBuildExtractValue(self.e.builder, closure, 0, "cl.fn"); + const env_ptr = c.LLVMBuildExtractValue(self.e.builder, closure, 1, "cl.env"); + + // Get the closure's declared parameter types from the IR type system + const callee_ir_ty = self.e.getRefIRType(call_op.callee); + const closure_params: ?[]const TypeId = if (callee_ir_ty) |cty| blk: { + if (!cty.isBuiltin()) { + const ci = self.e.ir_mod.types.get(cty); + if (ci == .closure) break :blk ci.closure.params; + } + break :blk null; + } else null; + + const has_ctx = self.e.ir_mod.has_implicit_ctx; + const user_args_offset_in_op: usize = if (has_ctx) 1 else 0; + const user_args_count: usize = call_op.args.len -| user_args_offset_in_op; + const ctx_slots: usize = if (has_ctx) 1 else 0; + const total_args = ctx_slots + 1 + user_args_count; // [ctx?] + env + user_args + + const args = self.e.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable; + defer self.e.alloc.free(args); + if (has_ctx) { + args[0] = self.e.resolveRef(call_op.args[0]); // ctx + } + args[ctx_slots] = env_ptr; + for (0..user_args_count) |j| { + args[ctx_slots + 1 + j] = self.e.resolveRef(call_op.args[user_args_offset_in_op + j]); + } + + // Build function type using declared param types (not arg types). + // closure_params is user-visible (no ctx, no env), so they line + // up with args[ctx_slots+1..]. + const ret_ty = self.e.toLLVMType(instruction.ty); + const param_tys = self.e.alloc.alloc(c.LLVMTypeRef, total_args) catch unreachable; + defer self.e.alloc.free(param_tys); + if (has_ctx) param_tys[0] = self.e.cached_ptr; // __sx_ctx + param_tys[ctx_slots] = self.e.cached_ptr; // env + if (closure_params) |cp| { + for (0..user_args_count) |j| { + const param_ir_ty = if (j < cp.len) cp[j] else null; + if (param_ir_ty) |pty| { + const llvm_pty = self.e.toLLVMType(pty); + param_tys[ctx_slots + 1 + j] = llvm_pty; + args[ctx_slots + 1 + j] = self.e.coerceArg(args[ctx_slots + 1 + j], llvm_pty); + } else { + param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]); + } + } + } else { + for (0..user_args_count) |j| { + param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]); + } + } + const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, @intCast(total_args), 0); + + const is_void = instruction.ty == .void; + const result = c.LLVMBuildCall2(self.e.builder, fn_ty, fn_ptr, args.ptr, @intCast(total_args), if (is_void) "" else "ccall"); + if (!is_void) { + self.e.mapRef(result); + } else { + self.e.advanceRefCounter(); + } + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 037d396..4712652 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -50,46 +50,46 @@ fn isIdentByte(b: u8) bool { /// spec across versions; locked to the documented order in /// ``. Slot numbers here MUST match the order of fields in /// the C `JNINativeInterface_` struct. -const Jni = struct { - const FindClass: u32 = 6; - const NewGlobalRef: u32 = 21; - const NewObject: u32 = 28; - const GetObjectClass: u32 = 31; - const GetMethodID: u32 = 33; +pub const Jni = struct { + pub const FindClass: u32 = 6; + pub const NewGlobalRef: u32 = 21; + pub const NewObject: u32 = 28; + pub const GetObjectClass: u32 = 31; + pub const GetMethodID: u32 = 33; // CallMethod (instance, varargs variant). Each numeric type // has its own slot — distinct ABI per return type, so the JNI // runtime dispatches the right arg-shuffle for each. - const CallObjectMethod: u32 = 34; - const CallBooleanMethod: u32 = 37; - const CallIntMethod: u32 = 49; - const CallLongMethod: u32 = 52; - const CallFloatMethod: u32 = 55; - const CallDoubleMethod: u32 = 58; - const CallVoidMethod: u32 = 61; + pub const CallObjectMethod: u32 = 34; + pub const CallBooleanMethod: u32 = 37; + pub const CallIntMethod: u32 = 49; + pub const CallLongMethod: u32 = 52; + pub const CallFloatMethod: u32 = 55; + pub const CallDoubleMethod: u32 = 58; + pub const CallVoidMethod: u32 = 61; // CallNonvirtualMethod (instance, super-dispatch variant). Used by // `super.method(args)` from inside a `#jni_main` Activity method body: // dispatch is bound to a specific class rather than going through the // vtable, so subclass overrides don't intercept the call. Signature: // `(JNIEnv*, jobject obj, jclass clazz, jmethodID, args...)`. - const CallNonvirtualObjectMethod: u32 = 64; - const CallNonvirtualBooleanMethod: u32 = 67; - const CallNonvirtualIntMethod: u32 = 79; - const CallNonvirtualLongMethod: u32 = 82; - const CallNonvirtualFloatMethod: u32 = 85; - const CallNonvirtualDoubleMethod: u32 = 88; - const CallNonvirtualVoidMethod: u32 = 91; + pub const CallNonvirtualObjectMethod: u32 = 64; + pub const CallNonvirtualBooleanMethod: u32 = 67; + pub const CallNonvirtualIntMethod: u32 = 79; + pub const CallNonvirtualLongMethod: u32 = 82; + pub const CallNonvirtualFloatMethod: u32 = 85; + pub const CallNonvirtualDoubleMethod: u32 = 88; + pub const CallNonvirtualVoidMethod: u32 = 91; // Static-dispatch siblings — `target` IS already a `jclass`, so // no `GetObjectClass` step. `GetStaticMethodID` returns a // method-ID that's bound to a class+method+sig like the instance // variant; `CallStaticMethod` dispatches without a `this`. - const GetStaticMethodID: u32 = 113; - const CallStaticObjectMethod: u32 = 114; - const CallStaticBooleanMethod: u32 = 117; - const CallStaticIntMethod: u32 = 129; - const CallStaticLongMethod: u32 = 132; - const CallStaticFloatMethod: u32 = 135; - const CallStaticDoubleMethod: u32 = 138; - const CallStaticVoidMethod: u32 = 141; + pub const GetStaticMethodID: u32 = 113; + pub const CallStaticObjectMethod: u32 = 114; + pub const CallStaticBooleanMethod: u32 = 117; + pub const CallStaticIntMethod: u32 = 129; + pub const CallStaticLongMethod: u32 = 132; + pub const CallStaticFloatMethod: u32 = 135; + pub const CallStaticDoubleMethod: u32 = 138; + pub const CallStaticVoidMethod: u32 = 141; }; // ── LLVMEmitter ───────────────────────────────────────────────────────── @@ -641,7 +641,7 @@ pub const LLVMEmitter = struct { /// (the ptr); otherwise return it unchanged. Used by JNI dispatch /// to feed string-literal method names + signatures to /// `GetMethodID`, which expects raw C strings. - fn extractSlicePtr(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { + pub fn extractSlicePtr(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { const val_ty = c.LLVMTypeOf(val); if (c.LLVMGetTypeKind(val_ty) != c.LLVMStructTypeKind) return val; if (c.LLVMCountStructElementTypes(val_ty) != 2) return val; @@ -653,7 +653,7 @@ pub const LLVMEmitter = struct { /// Load a JNI vtable function pointer at the given offset. `ifs` /// is the `JNINativeInterface*` loaded from `JNIEnv*`. Treats the /// vtable as an array of opaque `ptr`s and indexes into it. - fn loadJniFn(self: *LLVMEmitter, ifs: c.LLVMValueRef, offset: u32, name: [*:0]const u8) c.LLVMValueRef { + pub fn loadJniFn(self: *LLVMEmitter, ifs: c.LLVMValueRef, offset: u32, name: [*:0]const u8) c.LLVMValueRef { const offset_val = c.LLVMConstInt(self.cached_i32, offset, 0); var idx = [_]c.LLVMValueRef{offset_val}; const slot = c.LLVMBuildInBoundsGEP2(self.builder, self.cached_ptr, ifs, &idx, 1, ""); @@ -664,7 +664,7 @@ pub const LLVMEmitter = struct { /// Cached on the emitter; all `objc_msg_send` instructions hand /// LLVMBuildCall2 their own per-call-site function type — the /// underlying function value is just an opaque `ptr` symbol. - fn getObjcMsgSendValue(self: *LLVMEmitter) c.LLVMValueRef { + pub fn getObjcMsgSendValue(self: *LLVMEmitter) c.LLVMValueRef { if (self.objc_msg_send_value) |v| return v; const name_z = "objc_msgSend"; if (c.LLVMGetNamedFunction(self.llvm_module, name_z)) |existing| { @@ -1411,492 +1411,10 @@ pub const LLVMEmitter = struct { .deref => |un| self.ops().emitDeref(instruction, un), // ── Calls ───────────────────────────────────────────── - .objc_msg_send => |msg| { - const msg_send = self.getObjcMsgSendValue(); - // Detect the sret case: >16 B non-HFA struct return. - // Same predicate as the plain-foreign-call path so the - // two arms stay in lockstep. - const raw_ret_ty = self.toLLVMType(instruction.ty); - const uses_sret = self.needsByval(instruction.ty, raw_ret_ty); - const ret_ty = if (uses_sret) self.cached_void else raw_ret_ty; - - // Slot layout: - // uses_sret = false → [recv, sel, args...] - // uses_sret = true → [sret_slot, recv, sel, args...] - const sret_off: usize = if (uses_sret) 1 else 0; - const total_params: usize = 2 + msg.args.len + sret_off; - const param_types = self.alloc.alloc(c.LLVMTypeRef, total_params) catch unreachable; - defer self.alloc.free(param_types); - const call_args = self.alloc.alloc(c.LLVMValueRef, total_params) catch unreachable; - defer self.alloc.free(call_args); - - var sret_slot: c.LLVMValueRef = null; - if (uses_sret) { - sret_slot = c.LLVMBuildAlloca(self.builder, raw_ret_ty, "objc.sret"); - param_types[0] = self.cached_ptr; - call_args[0] = sret_slot; - } - - // recv (typed *void from the IR) - param_types[sret_off] = self.cached_ptr; - call_args[sret_off] = self.coerceArg(self.resolveRef(msg.recv), self.cached_ptr); - // sel (loaded SEL — opaque ptr) - param_types[sret_off + 1] = self.cached_ptr; - call_args[sret_off + 1] = self.coerceArg(self.resolveRef(msg.sel), self.cached_ptr); - // additional args take their IR types, with ABI - // coercion applied so structs / strings decay the - // same way they do for any C foreign call. - for (msg.args, 0..) |arg_ref, i| { - const raw_ty = self.getRefIRType(arg_ref) orelse .void; - const raw_llvm = self.toLLVMType(raw_ty); - const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm); - param_types[i + 2 + sret_off] = coerced_ty; - call_args[i + 2 + sret_off] = self.coerceArg(self.resolveRef(arg_ref), coerced_ty); - } - - const fn_ty = c.LLVMFunctionType(ret_ty, param_types.ptr, @intCast(total_params), 0); - const call_label: [*:0]const u8 = if (instruction.ty == .void or uses_sret) "" else "objc.msg"; - var result = c.LLVMBuildCall2(self.builder, fn_ty, msg_send, call_args.ptr, @intCast(total_params), call_label); - if (uses_sret) { - // Tag the call's arg 0 (sret slot) with the sret - // attribute so the AArch64 / SysV backends route - // through the x8 / hidden-pointer convention. - const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4); - const sret_attr = c.LLVMCreateTypeAttribute(self.context, sret_kind, raw_ret_ty); - const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1)); - c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr); - result = c.LLVMBuildLoad2(self.builder, raw_ret_ty, sret_slot, "objc.sret.load"); - } - // Always mapRef — the IR Ref counter for this - // instruction advances regardless of return type, - // so skipping it would misalign every subsequent - // ref lookup in this function. - self.mapRef(result); - }, - .jni_msg_send => |msg| { - // JNI vtable indirection: - // ifs = *env // JNINativeInterface* - // instance: cls = ifs[GetObjectClass](env, target) - // mid = ifs[GetMethodID](env, cls, name, sig) - // ifs[CallMethod](env, target, mid, args...) - // static: target IS the jclass — skip GetObjectClass - // mid = ifs[GetStaticMethodID](env, target, name, sig) - // ifs[CallStaticMethod](env, target, mid, args...) - // ctor: cls = ifs[FindClass](env, parent_class_path) - // mid = ifs[GetMethodID](env, cls, "", sig) - // ifs[NewObject](env, cls, mid, args...) → jobject - // nonvirt: handled below via FindClass + GetMethodID + - // CallNonvirtualMethod. - // The cached path (msg.cache_key != null) still shares one - // (jclass GlobalRef, jmethodID) pair per literal (name, sig). - if (msg.is_constructor) { - self.emitJniConstructor(msg, instruction.ty); - return; - } - const ret_ty_id = instruction.ty; - const is_pointer_ret = switch (self.ir_mod.types.get(ret_ty_id)) { - .pointer, .many_pointer => true, - else => false, - }; - const call_method_offset: u32 = if (msg.is_static) blk: { - if (is_pointer_ret) break :blk Jni.CallStaticObjectMethod; - break :blk switch (ret_ty_id) { - .void => Jni.CallStaticVoidMethod, - .s32 => Jni.CallStaticIntMethod, - .s64 => Jni.CallStaticLongMethod, - .f32 => Jni.CallStaticFloatMethod, - .f64 => Jni.CallStaticDoubleMethod, - .bool => Jni.CallStaticBooleanMethod, - else => { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - }, - }; - } else if (msg.is_nonvirtual) blk: { - if (is_pointer_ret) break :blk Jni.CallNonvirtualObjectMethod; - break :blk switch (ret_ty_id) { - .void => Jni.CallNonvirtualVoidMethod, - .s32 => Jni.CallNonvirtualIntMethod, - .s64 => Jni.CallNonvirtualLongMethod, - .f32 => Jni.CallNonvirtualFloatMethod, - .f64 => Jni.CallNonvirtualDoubleMethod, - .bool => Jni.CallNonvirtualBooleanMethod, - else => { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - }, - }; - } else blk: { - if (is_pointer_ret) break :blk Jni.CallObjectMethod; - break :blk switch (ret_ty_id) { - .void => Jni.CallVoidMethod, - .s32 => Jni.CallIntMethod, - .s64 => Jni.CallLongMethod, - .f32 => Jni.CallFloatMethod, - .f64 => Jni.CallDoubleMethod, - .bool => Jni.CallBooleanMethod, - else => { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - }, - }; - }; - const get_mid_offset: u32 = if (msg.is_static) Jni.GetStaticMethodID else Jni.GetMethodID; - - const env = self.resolveRef(msg.env); - const target = self.resolveRef(msg.target); - // String literals lower as `{ptr, i64}` slices in sx IR; - // JNI's `GetMethodID` expects raw C strings, so extract - // field 0 when the source is a slice. - const name_ptr = self.extractSlicePtr(self.resolveRef(msg.name)); - const sig_ptr = self.extractSlicePtr(self.resolveRef(msg.sig)); - - const ifs = c.LLVMBuildLoad2(self.builder, self.cached_ptr, env, "jni.ifs"); - - // Method-ID resolution. When `name` and `sig` are both - // string literals the call site participates in - // `(name, sig)` slot interning (step 1.17): a shared - // pair of static globals holds the `jclass` GlobalRef - // and the `jmethodID`, populated lazily on the first - // call to any matching site. Non-literal sites fall - // back to the per-call `GetObjectClass + GetMethodID` - // sequence (1.15 shape). - const mid = if (msg.cache_key) |ck| blk: { - const pair = self.ffiCtors().getOrCreateJniSlots(ck.name_str, ck.sig_str); - const cached_mid = c.LLVMBuildLoad2(self.builder, self.cached_ptr, pair.mid_slot, "jni.cached.mid"); - const is_cached = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cached_mid, c.LLVMConstNull(self.cached_ptr), "jni.is.cached"); - - const cur_fn = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder)); - const miss_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "jni.miss"); - const cont_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "jni.cont"); - const before_bb = c.LLVMGetInsertBlock(self.builder); - _ = c.LLVMBuildCondBr(self.builder, is_cached, cont_bb, miss_bb); - - // Miss path: - // instance: GetObjectClass → NewGlobalRef → GetMethodID - // static: target IS class → NewGlobalRef(target) → GetStaticMethodID - c.LLVMPositionBuilderAtEnd(self.builder, miss_bb); - const local_cls = if (msg.is_static) target else inst_cls: { - const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass"); - var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; - const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0); - var gocls_args = [_]c.LLVMValueRef{ env, target }; - break :inst_cls c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); - }; - const new_global_ref = self.loadJniFn(ifs, Jni.NewGlobalRef, "jni.NewGlobalRef"); - var ngref_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; - const ngref_ty = c.LLVMFunctionType(self.cached_ptr, &ngref_params, 2, 0); - var ngref_args = [_]c.LLVMValueRef{ env, local_cls }; - const global_cls = c.LLVMBuildCall2(self.builder, ngref_ty, new_global_ref, &ngref_args, 2, "jni.global.cls"); - _ = c.LLVMBuildStore(self.builder, global_cls, pair.cls_slot); - const get_mid = self.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID"); - var gmid_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.cached_ptr, self.cached_ptr }; - const gmid_ty = c.LLVMFunctionType(self.cached_ptr, &gmid_params, 4, 0); - var gmid_args = [_]c.LLVMValueRef{ env, global_cls, name_ptr, sig_ptr }; - const fresh_mid = c.LLVMBuildCall2(self.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.fresh.mid"); - _ = c.LLVMBuildStore(self.builder, fresh_mid, pair.mid_slot); - const miss_end_bb = c.LLVMGetInsertBlock(self.builder); - _ = c.LLVMBuildBr(self.builder, cont_bb); - - // Cont: phi the cached vs fresh mid. - c.LLVMPositionBuilderAtEnd(self.builder, cont_bb); - const phi = c.LLVMBuildPhi(self.builder, self.cached_ptr, "jni.mid"); - var phi_vals = [_]c.LLVMValueRef{ cached_mid, fresh_mid }; - var phi_blocks = [_]c.LLVMBasicBlockRef{ before_bb, miss_end_bb }; - c.LLVMAddIncoming(phi, &phi_vals, &phi_blocks, 2); - break :blk phi; - } else blk: { - const cls = if (msg.is_static) target else if (msg.is_nonvirtual) nonvirt_cls: { - // `super.method(args)`: dispatch is bound to a - // specific class (the parent), not subclass-override. - // Resolve via FindClass(parent_path). No caching yet — - // per-call lookup. The parent path is a NUL-terminated - // C string emitted as a private LLVM global. - const path = msg.parent_class_path orelse ""; - const path_global = self.emitCStringGlobal(path, "jni.parent.path"); - const find_class = self.loadJniFn(ifs, Jni.FindClass, "jni.FindClass"); - var fc_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; - const fc_ty = c.LLVMFunctionType(self.cached_ptr, &fc_params, 2, 0); - var fc_args = [_]c.LLVMValueRef{ env, path_global }; - break :nonvirt_cls c.LLVMBuildCall2(self.builder, fc_ty, find_class, &fc_args, 2, "jni.parent.cls"); - } else inst_cls: { - const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass"); - var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; - const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0); - var gocls_args = [_]c.LLVMValueRef{ env, target }; - break :inst_cls c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); - }; - const get_mid = self.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID"); - var gmid_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.cached_ptr, self.cached_ptr }; - const gmid_ty = c.LLVMFunctionType(self.cached_ptr, &gmid_params, 4, 0); - var gmid_args = [_]c.LLVMValueRef{ env, cls, name_ptr, sig_ptr }; - const mid_val = c.LLVMBuildCall2(self.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.mid"); - if (msg.is_nonvirtual) { - // Stash cls in a dummy slot so the call site below - // can pick it up. Easiest path: do the call right - // here and return Ref.none, but we need to keep the - // outer phi shape. Instead, return both via tuple - // through an auxiliary local — simplest is to attach - // `cls` to a per-invocation slot. Use a stack alloca. - const cls_slot = c.LLVMBuildAlloca(self.builder, self.cached_ptr, "jni.parent.cls.slot"); - _ = c.LLVMBuildStore(self.builder, cls, cls_slot); - // Tag the slot pointer onto the phi result via the - // generated metadata: we'll re-extract by re-running - // FindClass — actually simpler: lower nonvirtual on - // the spot below. Drop the implicit `break` here: - const call_fn = self.loadJniFn(ifs, call_method_offset, "jni.callfn.nonvirtual"); - const raw_ret = self.toLLVMType(ret_ty_id); - const total_call_params_nv: usize = 4 + msg.args.len; - const call_param_types_nv = self.alloc.alloc(c.LLVMTypeRef, total_call_params_nv) catch unreachable; - defer self.alloc.free(call_param_types_nv); - const call_args_nv = self.alloc.alloc(c.LLVMValueRef, total_call_params_nv) catch unreachable; - defer self.alloc.free(call_args_nv); - call_param_types_nv[0] = self.cached_ptr; - call_param_types_nv[1] = self.cached_ptr; - call_param_types_nv[2] = self.cached_ptr; - call_param_types_nv[3] = self.cached_ptr; - call_args_nv[0] = env; - call_args_nv[1] = target; - call_args_nv[2] = cls; - call_args_nv[3] = mid_val; - for (msg.args, 0..) |arg_ref, i| { - const raw_ty = self.getRefIRType(arg_ref) orelse .void; - const raw_llvm = self.toLLVMType(raw_ty); - const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm); - call_param_types_nv[i + 4] = coerced_ty; - call_args_nv[i + 4] = self.coerceArg(self.resolveRef(arg_ref), coerced_ty); - } - const call_fn_ty_nv = c.LLVMFunctionType(raw_ret, call_param_types_nv.ptr, @intCast(total_call_params_nv), 0); - const label_nv: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.nonvirtual.ret"; - const result_nv = c.LLVMBuildCall2(self.builder, call_fn_ty_nv, call_fn, call_args_nv.ptr, @intCast(total_call_params_nv), label_nv); - self.mapRef(result_nv); - return; - } - break :blk mid_val; - }; - - // CallMethod: (JNIEnv*, jobject, jmethodID, args...) -> RetTy - const call_fn = self.loadJniFn(ifs, call_method_offset, "jni.callfn"); - const raw_ret = self.toLLVMType(ret_ty_id); - const total_call_params: usize = 3 + msg.args.len; - const call_param_types = self.alloc.alloc(c.LLVMTypeRef, total_call_params) catch unreachable; - defer self.alloc.free(call_param_types); - const call_args = self.alloc.alloc(c.LLVMValueRef, total_call_params) catch unreachable; - defer self.alloc.free(call_args); - call_param_types[0] = self.cached_ptr; - call_param_types[1] = self.cached_ptr; - call_param_types[2] = self.cached_ptr; - call_args[0] = env; - call_args[1] = target; - call_args[2] = mid; - for (msg.args, 0..) |arg_ref, i| { - const raw_ty = self.getRefIRType(arg_ref) orelse .void; - const raw_llvm = self.toLLVMType(raw_ty); - const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm); - call_param_types[i + 3] = coerced_ty; - call_args[i + 3] = self.coerceArg(self.resolveRef(arg_ref), coerced_ty); - } - const call_fn_ty = c.LLVMFunctionType(raw_ret, call_param_types.ptr, @intCast(total_call_params), 0); - const label: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.ret"; - const result = c.LLVMBuildCall2(self.builder, call_fn_ty, call_fn, call_args.ptr, @intCast(total_call_params), label); - self.mapRef(result); - }, - .call => |call_op| { - // Evaluate comptime functions at compile time - const callee_func = &self.ir_mod.functions.items[call_op.callee.index()]; - if (callee_func.is_comptime and call_op.args.len == 0) { - var interp_inst = Interpreter.init(self.ir_mod, self.alloc); - interp_inst.build_config = &self.build_config; - if (self.import_sources) |sm| interp_inst.setSourceMap(sm); - defer interp_inst.deinit(); - if (interp_inst.call(call_op.callee, &.{})) |result| { - if (result.asInt()) |v| { - self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @bitCast(v), 0)); - return; - } else if (result.asFloat()) |v| { - self.mapRef(c.LLVMConstReal(self.toLLVMType(instruction.ty), v)); - return; - } else if (result.asBool()) |v| { - self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @intFromBool(v), 0)); - return; - } else if (result == .string) { - self.mapRef(self.emitStringConstant(result.string)); - return; - } - } else |_| {} - } - const callee = self.func_map.get(call_op.callee.index()) orelse { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - }; - const callee_needs_c_abi = callee_func.is_extern or callee_func.call_conv == .c; - const callee_raw_ret = self.toLLVMType(callee_func.ret); - const callee_uses_sret = callee_needs_c_abi and self.needsByval(callee_func.ret, callee_raw_ret); - - // When the callee uses sret, prepend an alloca for the result. - // Index alignment: actual_args[0] = sret_slot; actual_args[i+1] = sx arg i. - const sret_off: usize = if (callee_uses_sret) 1 else 0; - const total_args = call_op.args.len + sret_off; - const args = self.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable; - defer self.alloc.free(args); - var sret_slot: c.LLVMValueRef = null; - if (callee_uses_sret) { - sret_slot = c.LLVMBuildAlloca(self.builder, callee_raw_ret, "sret.slot"); - args[0] = sret_slot; - } - for (call_op.args, 0..) |arg_ref, j| { - args[j + sret_off] = self.resolveRef(arg_ref); - } - const arg_count: c_uint = @intCast(total_args); - - // Get the function type from LLVM and coerce arguments - const fn_ty = c.LLVMGlobalGetValueType(callee); - const param_count = c.LLVMCountParamTypes(fn_ty); - if (param_count > 0) { - const param_types = self.alloc.alloc(c.LLVMTypeRef, param_count) catch unreachable; - defer self.alloc.free(param_types); - c.LLVMGetParamTypes(fn_ty, param_types.ptr); - for (0..@min(args.len, param_count)) |j| { - // The sret slot is already a properly-typed pointer; skip coercion. - if (callee_uses_sret and j == 0) continue; - const fn_param_idx = j - sret_off; - // Materialize byval args before coercion so we pass a ptr instead of the struct value. - if (callee_needs_c_abi and fn_param_idx < callee_func.params.len) { - const ir_ty = callee_func.params[fn_param_idx].ty; - const raw_struct = self.toLLVMType(ir_ty); - if (self.needsByval(ir_ty, raw_struct)) { - args[j] = self.materializeByvalArg(args[j], raw_struct); - continue; - } - } - args[j] = self.coerceArg(args[j], param_types[j]); - } - } - // A `void`/`noreturn` call has no value, so it must stay - // unnamed (LLVM rejects a named void result). - const call_is_void_like = instruction.ty == .void or instruction.ty == .noreturn; - const call_label: [*:0]const u8 = if (call_is_void_like or callee_uses_sret) "" else "call"; - var result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, call_label); - if (callee_uses_sret) { - // Mirror the function-decl `sret()` attribute on the call site so the - // LLVM backend lowers arg 0 via x8 (AAPCS64) / hidden ptr (SysV AMD64). - const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4); - const sret_attr = c.LLVMCreateTypeAttribute(self.context, sret_kind, callee_raw_ret); - const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1)); - c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr); - // Load the actual struct value the callee wrote into the slot. - result = c.LLVMBuildLoad2(self.builder, callee_raw_ret, sret_slot, "sret.load"); - } else if (!call_is_void_like and callee_func.is_extern) { - // Coerce ABI return value (e.g. i64 / [2 x i64]) back to IR struct type if needed - const expected_ty = self.toLLVMType(instruction.ty); - result = self.coerceArg(result, expected_ty); - } - self.mapRef(result); - }, - .call_indirect => |call_op| { - const callee = self.resolveRef(call_op.callee); - const arg_count: c_uint = @intCast(call_op.args.len); - const args = self.alloc.alloc(c.LLVMValueRef, call_op.args.len) catch unreachable; - defer self.alloc.free(args); - for (call_op.args, 0..) |arg_ref, j| { - args[j] = self.resolveRef(arg_ref); - } - - // Get callee's IR type to resolve parameter types accurately - const callee_ir_ty = self.getRefIRType(call_op.callee); - const fn_params: ?[]const @import("types.zig").TypeId = if (callee_ir_ty) |cty| blk: { - if (!cty.isBuiltin()) { - const ci = self.ir_mod.types.get(cty); - switch (ci) { - .function => |f| break :blk f.params, - .closure => |cl| break :blk cl.params, - else => {}, - } - } - break :blk null; - } else null; - - // Read the fn-pointer type's calling convention. Only `.c` opts - // into the C-ABI byval coercion for >16B aggregate params. - const fp_is_c_abi: bool = if (callee_ir_ty) |cty| blk: { - if (!cty.isBuiltin()) { - const ci = self.ir_mod.types.get(cty); - if (ci == .function and ci.function.call_conv == .c) break :blk true; - } - break :blk false; - } else false; - - // Default-conv fn-pointers under implicit-ctx carry a hidden - // `*void` (the implicit __sx_ctx) at LLVM slot 0. The IR fn - // type does not include it, so shift fn_params lookups by 1. - const fp_ctx_slots: usize = if (callee_ir_ty) |cty| blk: { - if (!self.ir_mod.has_implicit_ctx) break :blk 0; - if (cty.isBuiltin()) break :blk 0; - const ci = self.ir_mod.types.get(cty); - switch (ci) { - .function => |f| break :blk if (f.call_conv == .c) @as(usize, 0) else 1, - else => break :blk 0, - } - } else 0; - - const ret_ty = if (callee_ir_ty) |cty| blk: { - if (!cty.isBuiltin()) { - const ci = self.ir_mod.types.get(cty); - switch (ci) { - .function => |f| break :blk self.toLLVMType(f.ret), - .closure => |cl| break :blk self.toLLVMType(cl.ret), - else => {}, - } - } - break :blk self.toLLVMType(instruction.ty); - } else self.toLLVMType(instruction.ty); - - const param_tys = self.alloc.alloc(c.LLVMTypeRef, call_op.args.len) catch unreachable; - defer self.alloc.free(param_tys); - if (fn_params) |fp| { - for (0..call_op.args.len) |j| { - // Slots 0..fp_ctx_slots are the implicit __sx_ctx - // (passed as opaque ptr; not in fp). - if (j < fp_ctx_slots) { - param_tys[j] = self.cached_ptr; - args[j] = self.coerceArg(args[j], self.cached_ptr); - continue; - } - const fp_idx = j - fp_ctx_slots; - if (fp_idx < fp.len) { - const raw_struct = self.toLLVMType(fp[fp_idx]); - if (fp_is_c_abi and self.needsByval(fp[fp_idx], raw_struct)) { - args[j] = self.materializeByvalArg(args[j], raw_struct); - param_tys[j] = self.cached_ptr; - continue; - } - var llvm_pty = raw_struct; - // Array params in fn-ptr calls decay to pointers (C ABI) - if (c.LLVMGetTypeKind(llvm_pty) == c.LLVMArrayTypeKind) { - llvm_pty = self.cached_ptr; - } - param_tys[j] = llvm_pty; - args[j] = self.coerceArg(args[j], llvm_pty); - } else { - param_tys[j] = c.LLVMTypeOf(args[j]); - } - } - } else { - for (args, 0..) |arg, j| { - param_tys[j] = c.LLVMTypeOf(arg); - } - } - const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, arg_count, 0); - const icall_void_like = instruction.ty == .void or instruction.ty == .noreturn; - var result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, if (icall_void_like) "" else "icall"); - - // Coerce call result to instruction's expected type - const expected_ty = self.toLLVMType(instruction.ty); - if (!icall_void_like and c.LLVMTypeOf(result) != expected_ty) { - result = self.coerceArg(result, expected_ty); - } - self.mapRef(result); - }, + .objc_msg_send => |msg| self.ops().emitObjcMsgSend(instruction, msg), + .jni_msg_send => |msg| self.ops().emitJniMsgSend(instruction, msg), + .call => |call_op| self.ops().emitCall(instruction, call_op), + .call_indirect => |call_op| self.ops().emitCallIndirect(instruction, call_op), // ── Terminators ──────────────────────────────────────── .ret => |un| { @@ -2400,196 +1918,9 @@ pub const LLVMEmitter = struct { }, // ── Call extensions ─────────────────────────────────────── - .call_builtin => |bi| { - // Builtins that map to libc functions or LLVM intrinsics - switch (bi.builtin) { - .sqrt, .sin, .cos, .floor => { - const val = self.resolveRef(bi.args[0]); - const val_ty = c.LLVMTypeOf(val); - const val_kind = c.LLVMGetTypeKind(val_ty); - if (val_kind == c.LLVMFloatTypeKind) { - const f = self.getOrDeclareMathF32(bi.builtin); - var args = [_]c.LLVMValueRef{val}; - self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF32Type(), f, &args, 1, @tagName(bi.builtin))); - } else { - const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.coerceArg(val, self.cached_f64) else val; - const f = self.getOrDeclareMathF64(bi.builtin); - var args = [_]c.LLVMValueRef{coerced}; - self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF64Type(), f, &args, 1, @tagName(bi.builtin))); - } - }, - .out => { - // out(str): extract ptr and len from string fat pointer, call write(1, ptr, len) - const str_val = self.resolveRef(bi.args[0]); - const raw_ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str.ptr"); - const str_len = c.LLVMBuildExtractValue(self.builder, str_val, 1, "str.len"); - // On wasm32, count param is i32 (size_t) - const count = if (self.target_config.isWasm32()) - c.LLVMBuildTrunc(self.builder, str_len, self.cached_i32, "len.tr") - else - str_len; - const write_fn = self.getOrDeclareWrite(); - var write_args = [_]c.LLVMValueRef{ - c.LLVMConstInt(self.cached_i32, 1, 0), // fd = stdout - raw_ptr, - count, - }; - _ = c.LLVMBuildCall2(self.builder, self.getWriteType(), write_fn, &write_args, 3, ""); - self.advanceRefCounter(); - }, - .type_name => { - // Dynamic `type_name(t)` at runtime: extract - // the TypeId from the arg (an Any-boxed Type - // value: tag=`.s64.index()`, value=tid), GEP - // into the compiler-emitted `__sx_type_names` - // global, load the string. The arg's LLVM - // shape is the `{i64, i64}` Any aggregate - // (because the IR-side arg type is `.any` - // when boxed); for unboxed direct call sites - // (the arg IR type is `.s64` from - // `const_type`), the value IS the TypeId - // index directly. - const arg_ref = bi.args[0]; - const arg_val = self.resolveRef(arg_ref); - const arg_ir_ty = self.getRefIRType(arg_ref) orelse @import("types.zig").TypeId.s64; - const tid_idx = blk: { - if (arg_ir_ty == .any) { - // Boxed: extract value field. - break :blk c.LLVMBuildExtractValue(self.builder, arg_val, 1, "tn.tid"); - } - // Bare i64 (TypeId index). - break :blk arg_val; - }; - const arr_global = self.reflection().getOrBuildTypeNameArray(); - const arr_len = self.type_name_array_len; - const string_ty = self.getStringStructType(); - const arr_ty = c.LLVMArrayType(string_ty, arr_len); - const zero = c.LLVMConstInt(self.cached_i64, 0, 0); - var indices = [2]c.LLVMValueRef{ zero, tid_idx }; - const gep = c.LLVMBuildInBoundsGEP2(self.builder, arr_ty, arr_global, &indices, 2, "tn.gep"); - const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "tn.load"); - self.mapRef(result); - }, - .type_eq => { - // Dynamic `type_eq(a, b)` — both args are - // Type values. Extract TypeId from each Any - // box (or use directly if `.s64`-typed), - // icmp eq. - const a = blk: { - const v = self.resolveRef(bi.args[0]); - const ty = self.getRefIRType(bi.args[0]) orelse @import("types.zig").TypeId.s64; - if (ty == .any) break :blk c.LLVMBuildExtractValue(self.builder, v, 1, "te.a"); - break :blk v; - }; - const b = blk: { - const v = self.resolveRef(bi.args[1]); - const ty = self.getRefIRType(bi.args[1]) orelse @import("types.zig").TypeId.s64; - if (ty == .any) break :blk c.LLVMBuildExtractValue(self.builder, v, 1, "te.b"); - break :blk v; - }; - const eq_res = c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, a, b, "te.eq"); - self.mapRef(eq_res); - }, - .has_impl => { - // Runtime has_impl needs a protocol-map - // snapshot — not wired yet. Silent false for - // now; the lower-time fold via - // `tryConstBoolCondition` covers every - // statically-resolvable call. - self.mapRef(c.LLVMConstInt(self.cached_i1, 0, 0)); - }, - else => { - // size_of, cast — handled by lowering or codegen glue - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - }, - } - }, - .compiler_call => { - // Compiler hooks are comptime-only; if one reaches emission, produce undef - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - }, - .call_closure => |call_op| { - // Closure: { fn_ptr, env }. - // - // ABI (when module.has_implicit_ctx): - // trampoline signature: (__sx_ctx, env, args...) - // call_op.args[0] = __sx_ctx (prepended by lowering) - // call_op.args[1..] = user args - // extracted env_ptr = inserted at LLVM slot 1 - // - // ABI (without implicit_ctx): - // trampoline signature: (env, args...) - // call_op.args = user args (no ctx prepend) - // extracted env_ptr = inserted at LLVM slot 0 - const closure = self.resolveRef(call_op.callee); - const cl_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(closure)); - if (cl_kind != c.LLVMStructTypeKind) { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - } - const fn_ptr = c.LLVMBuildExtractValue(self.builder, closure, 0, "cl.fn"); - const env_ptr = c.LLVMBuildExtractValue(self.builder, closure, 1, "cl.env"); - - // Get the closure's declared parameter types from the IR type system - const callee_ir_ty = self.getRefIRType(call_op.callee); - const closure_params: ?[]const @import("types.zig").TypeId = if (callee_ir_ty) |cty| blk: { - if (!cty.isBuiltin()) { - const ci = self.ir_mod.types.get(cty); - if (ci == .closure) break :blk ci.closure.params; - } - break :blk null; - } else null; - - const has_ctx = self.ir_mod.has_implicit_ctx; - const user_args_offset_in_op: usize = if (has_ctx) 1 else 0; - const user_args_count: usize = call_op.args.len -| user_args_offset_in_op; - const ctx_slots: usize = if (has_ctx) 1 else 0; - const total_args = ctx_slots + 1 + user_args_count; // [ctx?] + env + user_args - - const args = self.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable; - defer self.alloc.free(args); - if (has_ctx) { - args[0] = self.resolveRef(call_op.args[0]); // ctx - } - args[ctx_slots] = env_ptr; - for (0..user_args_count) |j| { - args[ctx_slots + 1 + j] = self.resolveRef(call_op.args[user_args_offset_in_op + j]); - } - - // Build function type using declared param types (not arg types). - // closure_params is user-visible (no ctx, no env), so they line - // up with args[ctx_slots+1..]. - const ret_ty = self.toLLVMType(instruction.ty); - const param_tys = self.alloc.alloc(c.LLVMTypeRef, total_args) catch unreachable; - defer self.alloc.free(param_tys); - if (has_ctx) param_tys[0] = self.cached_ptr; // __sx_ctx - param_tys[ctx_slots] = self.cached_ptr; // env - if (closure_params) |cp| { - for (0..user_args_count) |j| { - const param_ir_ty = if (j < cp.len) cp[j] else null; - if (param_ir_ty) |pty| { - const llvm_pty = self.toLLVMType(pty); - param_tys[ctx_slots + 1 + j] = llvm_pty; - args[ctx_slots + 1 + j] = self.coerceArg(args[ctx_slots + 1 + j], llvm_pty); - } else { - param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]); - } - } - } else { - for (0..user_args_count) |j| { - param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]); - } - } - const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, @intCast(total_args), 0); - - const is_void = instruction.ty == .void; - const result = c.LLVMBuildCall2(self.builder, fn_ty, fn_ptr, args.ptr, @intCast(total_args), if (is_void) "" else "ccall"); - if (!is_void) { - self.mapRef(result); - } else { - self.advanceRefCounter(); - } - }, + .call_builtin => |bi| self.ops().emitCallBuiltin(instruction, bi), + .compiler_call => self.ops().emitCompilerCall(instruction), + .call_closure => |call_op| self.ops().emitCallClosure(instruction, call_op), // ── Tuple ops ──────────────────────────────────────────── .tuple_init => |agg| { @@ -3244,7 +2575,7 @@ pub const LLVMEmitter = struct { return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 3, 0); } - fn getOrDeclareMathF64(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef { + pub fn getOrDeclareMathF64(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef { const name: [*:0]const u8 = switch (id) { .sqrt => "sqrt", .sin => "sin", @@ -3256,12 +2587,12 @@ pub const LLVMEmitter = struct { return c.LLVMAddFunction(self.llvm_module, name, self.getMathF64Type()); } - fn getMathF64Type(self: *LLVMEmitter) c.LLVMTypeRef { + pub fn getMathF64Type(self: *LLVMEmitter) c.LLVMTypeRef { var param_types = [_]c.LLVMTypeRef{self.cached_f64}; return c.LLVMFunctionType(self.cached_f64, ¶m_types, 1, 0); } - fn getOrDeclareMathF32(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef { + pub fn getOrDeclareMathF32(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef { const name: [*:0]const u8 = switch (id) { .sqrt => "sqrtf", .sin => "sinf", @@ -3273,7 +2604,7 @@ pub const LLVMEmitter = struct { return c.LLVMAddFunction(self.llvm_module, name, self.getMathF32Type()); } - fn getMathF32Type(self: *LLVMEmitter) c.LLVMTypeRef { + pub fn getMathF32Type(self: *LLVMEmitter) c.LLVMTypeRef { var param_types = [_]c.LLVMTypeRef{self.cached_f32}; return c.LLVMFunctionType(self.cached_f32, ¶m_types, 1, 0); } @@ -3286,12 +2617,12 @@ pub const LLVMEmitter = struct { return c.LLVMAddFunction(self.llvm_module, "memcmp", fn_ty); } - fn getOrDeclareWrite(self: *LLVMEmitter) c.LLVMValueRef { + pub fn getOrDeclareWrite(self: *LLVMEmitter) c.LLVMValueRef { if (c.LLVMGetNamedFunction(self.llvm_module, "write")) |f| return f; return c.LLVMAddFunction(self.llvm_module, "write", self.getWriteType()); } - fn getWriteType(self: *LLVMEmitter) c.LLVMTypeRef { + pub fn getWriteType(self: *LLVMEmitter) c.LLVMTypeRef { // write(fd: i32, buf: ptr, count: size_t) → ssize_t const st = self.sizeType(); var param_types = [_]c.LLVMTypeRef{ self.cached_i32, self.cached_ptr, st }; @@ -3628,7 +2959,7 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } - fn ffiCtors(self: *LLVMEmitter) llvm_ffi_ctors.FfiCtors { + pub fn ffiCtors(self: *LLVMEmitter) llvm_ffi_ctors.FfiCtors { return .{ .e = self }; } @@ -3659,7 +2990,7 @@ pub const LLVMEmitter = struct { return self.abiLowering().needsByval(ir_ty, raw_llvm_ty); } - fn materializeByvalArg(self: *LLVMEmitter, val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef) c.LLVMValueRef { + pub fn materializeByvalArg(self: *LLVMEmitter, val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef) c.LLVMValueRef { return self.abiLowering().materializeByvalArg(val, struct_ty); } @@ -3777,7 +3108,7 @@ pub const LLVMEmitter = struct { /// Emit a NUL-terminated C string as a private LLVM global and return /// the pointer to its first byte. Used for FindClass(env, "") etc. /// where the runtime expects raw `const char *`, not the sx slice shape. - fn emitCStringGlobal(self: *LLVMEmitter, str: []const u8, name: [*:0]const u8) c.LLVMValueRef { + pub fn emitCStringGlobal(self: *LLVMEmitter, str: []const u8, name: [*:0]const u8) c.LLVMValueRef { const z = self.alloc.dupeZ(u8, str) catch unreachable; defer self.alloc.free(z); return c.LLVMBuildGlobalStringPtr(self.builder, z.ptr, name); @@ -3787,7 +3118,7 @@ pub const LLVMEmitter = struct { /// `FindClass(env, parent_class_path)` → `GetMethodID(env, clazz, /// "", sig)` → `NewObject(env, clazz, mid, args...)`. Returns /// the new jobject. Per-call lookups — no caching yet. - fn emitJniConstructor(self: *LLVMEmitter, msg: ir_inst.JniMsgSend, ret_ty_id: TypeId) void { + pub fn emitJniConstructor(self: *LLVMEmitter, msg: ir_inst.JniMsgSend, ret_ty_id: TypeId) void { const env = self.resolveRef(msg.env); const sig_ptr = self.extractSlicePtr(self.resolveRef(msg.sig)); const name_ptr = self.extractSlicePtr(self.resolveRef(msg.name)); From 1be16511ec35542d0475f0566ba3f473f8d1e734 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 12:03:45 +0300 Subject: [PATCH 13/26] refactor(backend): move aggregate handlers into ops.zig (A7.4 slice d) Relocate the struct, enum, union, array/slice, tuple, and optional opcode handler bodies out of emitInst into the existing Ops facade. Each moved arm now delegates via self.ops().emit(...); shared infra stays on LLVMEmitter, with resolveAggregate/resolveGepStructType widened to pub as the GEP handlers require. Pure relocation, behavior-preserving: zero snapshot churn (361/0). --- src/backend/llvm/ops.zig | 559 ++++++++++++++++++++++++++++++++++++++- src/ir/emit_llvm.zig | 537 ++----------------------------------- 2 files changed, 574 insertions(+), 522 deletions(-) diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index f5174bc..ee5e639 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -11,6 +11,10 @@ const Interpreter = interp_mod.Interpreter; const Inst = ir_inst.Inst; const BinOp = ir_inst.BinOp; const UnaryOp = ir_inst.UnaryOp; +const Aggregate = ir_inst.Aggregate; +const FieldAccess = ir_inst.FieldAccess; +const EnumInit = ir_inst.EnumInit; +const Subslice = ir_inst.Subslice; const Store = ir_inst.Store; const Conversion = ir_inst.Conversion; const GlobalId = ir_inst.GlobalId; @@ -27,13 +31,15 @@ const StringId = ir_types.StringId; /// Instruction-emission handlers for `emitInst`: the constant, arithmetic, /// bitwise, comparison, logical, memory, globals, conversion, pointer, and /// call opcodes (direct/indirect/objc/jni dispatch plus builtin, compiler, -/// and closure calls). A backend `*LLVMEmitter` facade (field `e`): each -/// method emits one opcode's LLVM IR via `self.e.*`. The shared infra these -/// bodies call back into (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/ -/// `emitCmpOrdered`/`emitStrCmp`/`emitStringConstant`/`reflection`/ -/// `emitConversion`/`coerceArg`/`getRefIRType`/`loadJniFn`/`extractSlicePtr`/ -/// `emitJniConstructor`) stays on `LLVMEmitter`. `emitInst`'s arms reach these -/// via `self.ops()`. +/// and closure calls), plus the aggregate ops (struct, enum, union, +/// array/slice, tuple, and optional). A backend `*LLVMEmitter` facade +/// (field `e`): each method emits one opcode's LLVM IR via `self.e.*`. The +/// shared infra these bodies call back into (`mapRef`/`resolveRef`/ +/// `matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/`emitStrCmp`/ +/// `emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/ +/// `getRefIRType`/`loadJniFn`/`extractSlicePtr`/`emitJniConstructor`/ +/// `resolveAggregate`/`resolveGepStructType`) stays on `LLVMEmitter`. +/// `emitInst`'s arms reach these via `self.ops()`. pub const Ops = struct { e: *LLVMEmitter, @@ -1144,4 +1150,543 @@ pub const Ops = struct { self.e.advanceRefCounter(); } } + + // ── Struct ops ──────────────────────────────────────────── + pub fn emitStructInit(self: Ops, instruction: *const Inst, agg: Aggregate) void { + const struct_ty = self.e.toLLVMType(instruction.ty); + const type_kind = c.LLVMGetTypeKind(struct_ty); + // For vector types, use InsertElement instead of InsertValue + const is_vector = type_kind == c.LLVMVectorTypeKind or type_kind == c.LLVMScalableVectorTypeKind; + // For array types, get expected element type for coercion + const is_array = type_kind == c.LLVMArrayTypeKind; + const elem_llvm_ty = if (is_array) c.LLVMGetElementType(struct_ty) else null; + var result = c.LLVMGetUndef(struct_ty); + for (agg.fields, 0..) |field_ref, i| { + var field_val = self.e.resolveRef(field_ref); + if (is_vector) { + // Coerce element to match vector element type + const vec_elem_ty = c.LLVMGetElementType(struct_ty); + const val_ty = c.LLVMTypeOf(field_val); + if (val_ty != vec_elem_ty) { + field_val = self.e.coerceArg(field_val, vec_elem_ty); + } + const idx = c.LLVMConstInt(self.e.cached_i32, @intCast(i), 0); + result = c.LLVMBuildInsertElement(self.e.builder, result, field_val, idx, "vi"); + } else { + // Coerce element to match array element type if needed + if (elem_llvm_ty) |elt| { + const val_ty = c.LLVMTypeOf(field_val); + if (val_ty != elt) { + const val_kind = c.LLVMGetTypeKind(val_ty); + const elt_kind = c.LLVMGetTypeKind(elt); + if (val_kind == c.LLVMIntegerTypeKind and elt_kind == c.LLVMIntegerTypeKind) { + const val_w = c.LLVMGetIntTypeWidth(val_ty); + const elt_w = c.LLVMGetIntTypeWidth(elt); + if (val_w > elt_w) { + field_val = c.LLVMBuildTrunc(self.e.builder, field_val, elt, "atrunc"); + } else if (val_w < elt_w) { + field_val = c.LLVMBuildZExt(self.e.builder, field_val, elt, "aext"); + } + } + } + } else if (type_kind == c.LLVMStructTypeKind) { + // Coerce struct field value to match declared field type + const n_elts = c.LLVMCountStructElementTypes(struct_ty); + if (n_elts > 0 and i < n_elts) { + const field_ty = c.LLVMStructGetTypeAtIndex(struct_ty, @intCast(i)); + const val_ty = c.LLVMTypeOf(field_val); + if (val_ty != field_ty) { + field_val = self.e.coerceArg(field_val, field_ty); + } + } + } + result = c.LLVMBuildInsertValue(self.e.builder, result, field_val, @intCast(i), "si"); + } + } + self.e.mapRef(result); + } + + pub fn emitStructGet(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + // Safety: null base means unresolved reference — emit undef + if (base == null) { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } else { + // Safety: check that base is an aggregate type (struct/array/vector), not scalar + const base_ty = c.LLVMTypeOf(base); + const base_ty_kind = c.LLVMGetTypeKind(base_ty); + if (base_ty_kind == c.LLVMVectorTypeKind or base_ty_kind == c.LLVMScalableVectorTypeKind) { + // Vector: use ExtractElement with an index + const idx = c.LLVMConstInt(self.e.cached_i32, @intCast(fa.field_index), 0); + const result = c.LLVMBuildExtractElement(self.e.builder, base, idx, "ve"); + self.e.mapRef(result); + } else if (base_ty_kind == c.LLVMStructTypeKind or base_ty_kind == c.LLVMArrayTypeKind) { + // Validate field index is in bounds + const n_fields = if (base_ty_kind == c.LLVMStructTypeKind) c.LLVMCountStructElementTypes(base_ty) else 0; + // Check builder has valid insert point + const insert_bb = c.LLVMGetInsertBlock(self.e.builder); + if (insert_bb == null or (n_fields == 0 and base_ty_kind == c.LLVMStructTypeKind) or (n_fields > 0 and fa.field_index >= n_fields)) { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } else { + const result = c.LLVMBuildExtractValue(self.e.builder, base, @intCast(fa.field_index), "sg"); + self.e.mapRef(result); + } + } else { + // Base is not an aggregate (e.g., placeholder undef of scalar type) + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + } + + pub fn emitStructGep(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base_ptr = self.e.resolveRef(fa.base); + // Safety: verify base is a pointer before GEP + const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); + if (base_ty_kind == c.LLVMPointerTypeKind) { + const struct_llvm_ty = if (fa.base_type) |bt| + self.e.toLLVMType(self.e.resolveAggregate(bt)) + else + self.e.resolveGepStructType(fa.base, instruction); + const st_kind = c.LLVMGetTypeKind(struct_llvm_ty); + if (st_kind == c.LLVMStructTypeKind or st_kind == c.LLVMArrayTypeKind) { + const result = c.LLVMBuildStructGEP2(self.e.builder, struct_llvm_ty, base_ptr, @intCast(fa.field_index), "gep"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + // ── Enum ops ───────────────────────────────────────────── + pub fn emitEnumInit(self: Ops, instruction: *const Inst, ei: EnumInit) void { + if (ei.payload.isNone()) { + // Simple enum (no payload) — just a tag integer + const ty = self.e.toLLVMType(instruction.ty); + const ty_kind = c.LLVMGetTypeKind(ty); + if (ty_kind == c.LLVMIntegerTypeKind) { + // Plain enum or builtin integer → integer constant + self.e.mapRef(c.LLVMConstInt(ty, ei.tag, 0)); + } else if (ty_kind == c.LLVMStructTypeKind) { + // Tagged union with no payload — header field 0 holds the tag + const header_ty = c.LLVMStructGetTypeAtIndex(ty, 0); + const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); + var result = c.LLVMGetUndef(ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, tag_val, 0, "ei.tag"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMConstInt(self.e.cached_i64, ei.tag, 0)); + } + } else { + // Tagged union with payload — { header, payload_bytes } + const union_ty = self.e.toLLVMType(instruction.ty); + const header_ty = c.LLVMStructGetTypeAtIndex(union_ty, 0); + const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); + const payload_val = self.e.resolveRef(ei.payload); + + // alloca union, store tag, bitcast payload area, store payload + const tmp = c.LLVMBuildAlloca(self.e.builder, union_ty, "ei.tmp"); + // Store tag at field 0 + const tag_ptr = c.LLVMBuildStructGEP2(self.e.builder, union_ty, tmp, 0, "ei.tagp"); + _ = c.LLVMBuildStore(self.e.builder, tag_val, tag_ptr); + // Store payload at field 1 (bitcast the byte array to payload type) + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, union_ty, tmp, 1, "ei.pp"); + const payload_typed_ptr = c.LLVMBuildBitCast(self.e.builder, payload_ptr, self.e.cached_ptr, "ei.pcast"); + _ = c.LLVMBuildStore(self.e.builder, payload_val, payload_typed_ptr); + // Load the whole union value + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, union_ty, tmp, "ei.val")); + } + } + + pub fn emitEnumTag(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + // Check if this is a plain enum (integer) or tagged union (struct with tag at 0) + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + // Tagged union — extract field 0 (tag) + var tag = c.LLVMBuildExtractValue(self.e.builder, val, 0, "etag"); + // Truncate to declared tag width if needed (e.g. i64 → i32 for u32 tags) + // This is essential for FFI unions where the i64 tag slot contains + // a smaller tag + uninitialized padding (e.g. SDL_Event's u32 type + u32 reserved) + const target_ty = self.e.toLLVMType(instruction.ty); + const extracted_bits = c.LLVMGetIntTypeWidth(c.LLVMTypeOf(tag)); + const target_bits = c.LLVMGetIntTypeWidth(target_ty); + if (target_bits < extracted_bits) { + tag = c.LLVMBuildTrunc(self.e.builder, tag, target_ty, "etag.trunc"); + } + self.e.mapRef(tag); + } else { + // Plain enum — the value IS the tag + self.e.mapRef(val); + } + } + + pub fn emitEnumPayload(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + const result_ty = self.e.toLLVMType(instruction.ty); + const base_ty = c.LLVMTypeOf(base); + const base_kind = c.LLVMGetTypeKind(base_ty); + if (base_kind == c.LLVMStructTypeKind) { + // Tagged union: alloca, store, GEP field 1 (payload area), bitcast, load + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ep.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, base_ty, tmp, 1, "ep.pp"); + const typed_ptr = c.LLVMBuildBitCast(self.e.builder, payload_ptr, self.e.cached_ptr, "ep.cast"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, result_ty, typed_ptr, "ep.val")); + } else { + self.e.mapRef(c.LLVMGetUndef(result_ty)); + } + } + + // ── Union ops ──────────────────────────────────────────── + pub fn emitUnionGet(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + const result_ty = self.e.toLLVMType(instruction.ty); + // Union field access: reinterpret the union's data area as the target type + const base_ty = c.LLVMTypeOf(base); + const kind = c.LLVMGetTypeKind(base_ty); + if (kind == c.LLVMStructTypeKind) { + // Tagged union { header, payload_bytes } — access payload at field 1 + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ug.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, base_ty, tmp, 1, "ug.pp"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, result_ty, payload_ptr, "ug.val")); + } else { + // Untagged union [N x i8] — alloca, store, reinterpret-load + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ug.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, result_ty, tmp, "ug.val")); + } + } + + pub fn emitUnionGep(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base_ptr = self.e.resolveRef(fa.base); + const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); + if (base_ty_kind == c.LLVMPointerTypeKind) { + const union_llvm_ty = if (fa.base_type) |bt| + self.e.toLLVMType(self.e.resolveAggregate(bt)) + else + self.e.resolveGepStructType(fa.base, instruction); + const st_kind = c.LLVMGetTypeKind(union_llvm_ty); + if (st_kind == c.LLVMStructTypeKind) { + // Tagged union — payload is at field 1 + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, union_llvm_ty, base_ptr, 1, "ugep.pp"); + self.e.mapRef(payload_ptr); + } else { + // Untagged union — data starts at offset 0 + self.e.mapRef(base_ptr); + } + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + // ── Array/Slice ops ─────────────────────────────────────── + pub fn emitIndexGet(self: Ops, instruction: *const Inst, bin: BinOp) void { + const base = self.e.resolveRef(bin.lhs); + const idx = self.e.resolveRef(bin.rhs); + const base_ty = c.LLVMTypeOf(base); + const kind = c.LLVMGetTypeKind(base_ty); + if (kind == c.LLVMVectorTypeKind or kind == c.LLVMScalableVectorTypeKind) { + // Vector — use extractelement + // Coerce index to i32 if needed + const idx32 = self.e.coerceArg(idx, self.e.cached_i32); + self.e.mapRef(c.LLVMBuildExtractElement(self.e.builder, base, idx32, "ve")); + } else if (kind == c.LLVMArrayTypeKind) { + // Fixed-size array value — alloca, store, GEP, load + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ig.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + const elem_ty = self.e.toLLVMType(instruction.ty); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), idx }; + const ptr = c.LLVMBuildGEP2(self.e.builder, base_ty, tmp, &indices, 2, "ig.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, elem_ty, ptr, "ig.val")); + } else if (kind == c.LLVMPointerTypeKind) { + // Pointer (many-pointer or raw ptr) — GEP + load + const elem_ty = self.e.toLLVMType(instruction.ty); + var indices = [_]c.LLVMValueRef{idx}; + const ptr = c.LLVMBuildGEP2(self.e.builder, elem_ty, base, &indices, 1, "ig.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, elem_ty, ptr, "ig.val")); + } else if (kind == c.LLVMStructTypeKind) { + // Slice/string {ptr, len} — extract ptr, GEP, load + const data = c.LLVMBuildExtractValue(self.e.builder, base, 0, "ig.data"); + const elem_ty = self.e.toLLVMType(instruction.ty); + var indices = [_]c.LLVMValueRef{idx}; + const ptr = c.LLVMBuildGEP2(self.e.builder, elem_ty, data, &indices, 1, "ig.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, elem_ty, ptr, "ig.val")); + } else { + // Non-aggregate base (lowering error) — emit undef + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + + pub fn emitIndexGep(self: Ops, instruction: *const Inst, bin: BinOp) void { + const base = self.e.resolveRef(bin.lhs); + const idx = self.e.resolveRef(bin.rhs); + const base_ty = c.LLVMTypeOf(base); + const kind = c.LLVMGetTypeKind(base_ty); + if (kind == c.LLVMArrayTypeKind) { + // Fixed-size array value — alloca, store, GEP + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "igp.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), idx }; + self.e.mapRef(c.LLVMBuildGEP2(self.e.builder, base_ty, tmp, &indices, 2, "igp.ptr")); + } else if (kind == c.LLVMPointerTypeKind) { + // Pointer — GEP with proper element type + const gep_elem = blk: { + // instruction.ty is the result type (ptr to element) + // Resolve the pointee type for the GEP element size + const info = self.e.ir_mod.types.get(instruction.ty); + break :blk switch (info) { + .pointer => |p| self.e.toLLVMType(p.pointee), + .many_pointer => |p| self.e.toLLVMType(p.element), + else => self.e.cached_i8, // fallback + }; + }; + var indices = [_]c.LLVMValueRef{idx}; + self.e.mapRef(c.LLVMBuildGEP2(self.e.builder, gep_elem, base, &indices, 1, "igp.ptr")); + } else if (kind == c.LLVMStructTypeKind) { + // Slice/string {ptr, len} — extract ptr, GEP with proper element type + const data = c.LLVMBuildExtractValue(self.e.builder, base, 0, "igp.data"); + const gep_elem = blk: { + const info = self.e.ir_mod.types.get(instruction.ty); + break :blk switch (info) { + .pointer => |p| self.e.toLLVMType(p.pointee), + .many_pointer => |p| self.e.toLLVMType(p.element), + else => self.e.cached_i8, + }; + }; + var indices = [_]c.LLVMValueRef{idx}; + self.e.mapRef(c.LLVMBuildGEP2(self.e.builder, gep_elem, data, &indices, 1, "igp.ptr")); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + pub fn emitLength(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMArrayTypeKind) { + const len = c.LLVMGetArrayLength2(val_ty); + self.e.mapRef(c.LLVMConstInt(self.e.cached_i64, len, 0)); + } else if (kind == c.LLVMStructTypeKind) { + // Slice/string {ptr, len} — extract field 1 (len) + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, 1, "len")); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_i64)); + } + } + + pub fn emitDataPtr(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, 0, "dptr")); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + pub fn emitSubslice(self: Ops, instruction: *const Inst, ss: Subslice) void { + const base = self.e.resolveRef(ss.base); + var lo = self.e.resolveRef(ss.lo); + var hi = self.e.resolveRef(ss.hi); + // Normalize lo/hi to i64 for consistent arithmetic (indices are unsigned) + if (c.LLVMTypeOf(lo) != self.e.cached_i64) { + lo = c.LLVMBuildZExt(self.e.builder, lo, self.e.cached_i64, "ss.lo64"); + } + if (c.LLVMTypeOf(hi) != self.e.cached_i64) { + hi = c.LLVMBuildZExt(self.e.builder, hi, self.e.cached_i64, "ss.hi64"); + } + const base_ty = c.LLVMTypeOf(base); + const base_kind = c.LLVMGetTypeKind(base_ty); + const slice_ty = self.e.toLLVMType(instruction.ty); + // Resolve element type from the result slice type for correct GEP stride + const elem_ty = blk: { + const info = self.e.ir_mod.types.get(instruction.ty); + break :blk switch (info) { + .slice => |s| self.e.toLLVMType(s.element), + else => self.e.cached_i8, + }; + }; + if (base_kind == c.LLVMStructTypeKind) { + // Slice/string: extract data ptr, GEP by lo + const data = c.LLVMBuildExtractValue(self.e.builder, base, 0, "ss.data"); + var lo_indices = [_]c.LLVMValueRef{lo}; + const new_ptr = c.LLVMBuildGEP2(self.e.builder, elem_ty, data, &lo_indices, 1, "ss.ptr"); + var new_len = c.LLVMBuildSub(self.e.builder, hi, lo, "ss.len"); + // Ensure length is i64 for slice struct {ptr, i64} + if (c.LLVMTypeOf(new_len) != self.e.cached_i64) { + new_len = c.LLVMBuildSExt(self.e.builder, new_len, self.e.cached_i64, "ss.ext"); + } + var result = c.LLVMGetUndef(slice_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_ptr, 0, "ss.wptr"); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_len, 1, "ss.wlen"); + self.e.mapRef(result); + } else if (base_kind == c.LLVMArrayTypeKind) { + // Array: alloca, GEP to element at lo, compute len + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ss.arr"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), lo }; + const new_ptr = c.LLVMBuildGEP2(self.e.builder, base_ty, tmp, &indices, 2, "ss.ptr"); + var new_len = c.LLVMBuildSub(self.e.builder, hi, lo, "ss.len"); + // Ensure length is i64 for slice struct {ptr, i64} + if (c.LLVMTypeOf(new_len) != self.e.cached_i64) { + new_len = c.LLVMBuildSExt(self.e.builder, new_len, self.e.cached_i64, "ss.ext"); + } + var result = c.LLVMGetUndef(slice_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_ptr, 0, "ss.wptr"); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_len, 1, "ss.wlen"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(slice_ty)); + } + } + + pub fn emitArrayToSlice(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const arr = self.e.resolveRef(un.operand); + const arr_ty = c.LLVMTypeOf(arr); + const arr_kind = c.LLVMGetTypeKind(arr_ty); + if (arr_kind == c.LLVMArrayTypeKind) { + const len = c.LLVMGetArrayLength2(arr_ty); + const tmp = c.LLVMBuildAlloca(self.e.builder, arr_ty, "a2s.tmp"); + _ = c.LLVMBuildStore(self.e.builder, arr, tmp); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), c.LLVMConstInt(self.e.cached_i64, 0, 0) }; + const elem_ptr = c.LLVMBuildGEP2(self.e.builder, arr_ty, tmp, &indices, 2, "a2s.ptr"); + const slice_ty = self.e.toLLVMType(instruction.ty); + var result = c.LLVMGetUndef(slice_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, elem_ptr, 0, "a2s.wptr"); + const len_val = c.LLVMConstInt(self.e.cached_i64, len, 0); + result = c.LLVMBuildInsertValue(self.e.builder, result, len_val, 1, "a2s.wlen"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + + // ── Tuple ops ──────────────────────────────────────────── + pub fn emitTupleInit(self: Ops, instruction: *const Inst, agg: Aggregate) void { + const tuple_ty = self.e.toLLVMType(instruction.ty); + var result = c.LLVMGetUndef(tuple_ty); + for (agg.fields, 0..) |field_ref, i| { + const field_val = self.e.resolveRef(field_ref); + result = c.LLVMBuildInsertValue(self.e.builder, result, field_val, @intCast(i), "ti"); + } + self.e.mapRef(result); + } + + pub fn emitTupleGet(self: Ops, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, base, @intCast(fa.field_index), "tg")); + } + + // ── Optional ops ───────────────────────────────────────── + pub fn emitOptionalWrap(self: Ops, instruction: *const Inst, un: UnaryOp) void { + var val = self.e.resolveRef(un.operand); + const opt_ty = self.e.toLLVMType(instruction.ty); + const opt_kind = c.LLVMGetTypeKind(opt_ty); + if (opt_kind == c.LLVMPointerTypeKind) { + // ?*T — pointer is the optional itself (null = none) + self.e.mapRef(val); + } else if (opt_kind == c.LLVMStructTypeKind) { + // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) + const num_fields = c.LLVMCountStructElementTypes(opt_ty); + const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(opt_ty, num_fields - 1) else self.e.cached_i1; + if (last_field_ty == self.e.cached_i1) { + // ?T → { T, i1 } — wrap value + true flag + const inner_ty = c.LLVMStructGetTypeAtIndex(opt_ty, 0); + val = self.e.coerceArg(val, inner_ty); + var result = c.LLVMGetUndef(opt_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, val, 0, "ow.val"); + result = c.LLVMBuildInsertValue(self.e.builder, result, c.LLVMConstInt(self.e.cached_i1, 1, 0), 1, "ow.has"); + self.e.mapRef(result); + } else { + // ?Closure → closure struct IS the optional, just pass through + self.e.mapRef(val); + } + } else { + self.e.mapRef(val); + } + } + + pub fn emitOptionalUnwrap(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) + const num_fields = c.LLVMCountStructElementTypes(val_ty); + const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.e.cached_i1; + if (last_field_ty == self.e.cached_i1) { + // { T, i1 } → extract field 0 + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, 0, "ou.val")); + } else { + // ?Closure → the struct itself is the value + self.e.mapRef(val); + } + } else { + // ?*T → pointer is the value itself + self.e.mapRef(val); + } + } + + pub fn emitOptionalHasValue(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) + const num_fields = c.LLVMCountStructElementTypes(val_ty); + const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.e.cached_i1; + if (last_field_ty == self.e.cached_i1) { + // { T, i1 } → extract has_value flag + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, num_fields - 1, "oh.has")); + } else { + // ?Closure {fn_ptr, env} → check if fn_ptr is null + const fn_ptr = c.LLVMBuildExtractValue(self.e.builder, val, 0, "oh.fn"); + self.e.mapRef(c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oh.nn")); + } + } else { + // ?*T → compare with null + const is_nonnull = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, val, c.LLVMConstNull(val_ty), "oh.nn"); + self.e.mapRef(is_nonnull); + } + } + + pub fn emitOptionalCoalesce(self: Ops, bin: BinOp) void { + // a ?? b — if a has value, use a's value; otherwise use b + const a = self.e.resolveRef(bin.lhs); + var b_val = self.e.resolveRef(bin.rhs); + const a_ty = c.LLVMTypeOf(a); + const kind = c.LLVMGetTypeKind(a_ty); + if (kind == c.LLVMStructTypeKind) { + const n_fields = c.LLVMCountStructElementTypes(a_ty); + const f1_ty = if (n_fields >= 2) c.LLVMStructGetTypeAtIndex(a_ty, 1) else null; + const is_ti1 = if (f1_ty) |ft| c.LLVMGetTypeKind(ft) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(ft) == 1 else false; + if (is_ti1) { + // Standard optional {T, i1}: extract has_value and unwrap + const has = c.LLVMBuildExtractValue(self.e.builder, a, 1, "oc.has"); + const unwrapped = c.LLVMBuildExtractValue(self.e.builder, a, 0, "oc.val"); + const uw_ty = c.LLVMTypeOf(unwrapped); + const b_ty = c.LLVMTypeOf(b_val); + if (uw_ty != b_ty) { + b_val = self.e.coerceArg(b_val, uw_ty); + } + self.e.mapRef(c.LLVMBuildSelect(self.e.builder, has, unwrapped, b_val, "oc.sel")); + } else { + // ?Closure {fn_ptr, env}: check if fn_ptr is null + const fn_ptr = c.LLVMBuildExtractValue(self.e.builder, a, 0, "oc.fn"); + const is_nonnull = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oc.nn"); + // Select the full closure struct, not just the fn_ptr + self.e.mapRef(c.LLVMBuildSelect(self.e.builder, is_nonnull, a, b_val, "oc.sel")); + } + } else { + // ?*T — select on null + const is_nonnull = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, a, c.LLVMConstNull(a_ty), "oc.nn"); + self.e.mapRef(c.LLVMBuildSelect(self.e.builder, is_nonnull, a, b_val, "oc.sel")); + } + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 4712652..5a9af27 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1512,410 +1512,26 @@ pub const LLVMEmitter = struct { }, // ── Struct ops ──────────────────────────────────────────── - .struct_init => |agg| { - const struct_ty = self.toLLVMType(instruction.ty); - const type_kind = c.LLVMGetTypeKind(struct_ty); - // For vector types, use InsertElement instead of InsertValue - const is_vector = type_kind == c.LLVMVectorTypeKind or type_kind == c.LLVMScalableVectorTypeKind; - // For array types, get expected element type for coercion - const is_array = type_kind == c.LLVMArrayTypeKind; - const elem_llvm_ty = if (is_array) c.LLVMGetElementType(struct_ty) else null; - var result = c.LLVMGetUndef(struct_ty); - for (agg.fields, 0..) |field_ref, i| { - var field_val = self.resolveRef(field_ref); - if (is_vector) { - // Coerce element to match vector element type - const vec_elem_ty = c.LLVMGetElementType(struct_ty); - const val_ty = c.LLVMTypeOf(field_val); - if (val_ty != vec_elem_ty) { - field_val = self.coerceArg(field_val, vec_elem_ty); - } - const idx = c.LLVMConstInt(self.cached_i32, @intCast(i), 0); - result = c.LLVMBuildInsertElement(self.builder, result, field_val, idx, "vi"); - } else { - // Coerce element to match array element type if needed - if (elem_llvm_ty) |elt| { - const val_ty = c.LLVMTypeOf(field_val); - if (val_ty != elt) { - const val_kind = c.LLVMGetTypeKind(val_ty); - const elt_kind = c.LLVMGetTypeKind(elt); - if (val_kind == c.LLVMIntegerTypeKind and elt_kind == c.LLVMIntegerTypeKind) { - const val_w = c.LLVMGetIntTypeWidth(val_ty); - const elt_w = c.LLVMGetIntTypeWidth(elt); - if (val_w > elt_w) { - field_val = c.LLVMBuildTrunc(self.builder, field_val, elt, "atrunc"); - } else if (val_w < elt_w) { - field_val = c.LLVMBuildZExt(self.builder, field_val, elt, "aext"); - } - } - } - } else if (type_kind == c.LLVMStructTypeKind) { - // Coerce struct field value to match declared field type - const n_elts = c.LLVMCountStructElementTypes(struct_ty); - if (n_elts > 0 and i < n_elts) { - const field_ty = c.LLVMStructGetTypeAtIndex(struct_ty, @intCast(i)); - const val_ty = c.LLVMTypeOf(field_val); - if (val_ty != field_ty) { - field_val = self.coerceArg(field_val, field_ty); - } - } - } - result = c.LLVMBuildInsertValue(self.builder, result, field_val, @intCast(i), "si"); - } - } - self.mapRef(result); - }, - .struct_get => |fa| { - const base = self.resolveRef(fa.base); - // Safety: null base means unresolved reference — emit undef - if (base == null) { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } else { - // Safety: check that base is an aggregate type (struct/array/vector), not scalar - const base_ty = c.LLVMTypeOf(base); - const base_ty_kind = c.LLVMGetTypeKind(base_ty); - if (base_ty_kind == c.LLVMVectorTypeKind or base_ty_kind == c.LLVMScalableVectorTypeKind) { - // Vector: use ExtractElement with an index - const idx = c.LLVMConstInt(self.cached_i32, @intCast(fa.field_index), 0); - const result = c.LLVMBuildExtractElement(self.builder, base, idx, "ve"); - self.mapRef(result); - } else if (base_ty_kind == c.LLVMStructTypeKind or base_ty_kind == c.LLVMArrayTypeKind) { - // Validate field index is in bounds - const n_fields = if (base_ty_kind == c.LLVMStructTypeKind) c.LLVMCountStructElementTypes(base_ty) else 0; - // Check builder has valid insert point - const insert_bb = c.LLVMGetInsertBlock(self.builder); - if (insert_bb == null or (n_fields == 0 and base_ty_kind == c.LLVMStructTypeKind) or (n_fields > 0 and fa.field_index >= n_fields)) { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } else { - const result = c.LLVMBuildExtractValue(self.builder, base, @intCast(fa.field_index), "sg"); - self.mapRef(result); - } - } else { - // Base is not an aggregate (e.g., placeholder undef of scalar type) - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } - } - }, - .struct_gep => |fa| { - const base_ptr = self.resolveRef(fa.base); - // Safety: verify base is a pointer before GEP - const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); - if (base_ty_kind == c.LLVMPointerTypeKind) { - const struct_llvm_ty = if (fa.base_type) |bt| - self.toLLVMType(self.resolveAggregate(bt)) - else - self.resolveGepStructType(fa.base, instruction); - const st_kind = c.LLVMGetTypeKind(struct_llvm_ty); - if (st_kind == c.LLVMStructTypeKind or st_kind == c.LLVMArrayTypeKind) { - const result = c.LLVMBuildStructGEP2(self.builder, struct_llvm_ty, base_ptr, @intCast(fa.field_index), "gep"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, + .struct_init => |agg| self.ops().emitStructInit(instruction, agg), + .struct_get => |fa| self.ops().emitStructGet(instruction, fa), + .struct_gep => |fa| self.ops().emitStructGep(instruction, fa), // ── Enum ops ───────────────────────────────────────────── - .enum_init => |ei| { - if (ei.payload.isNone()) { - // Simple enum (no payload) — just a tag integer - const ty = self.toLLVMType(instruction.ty); - const ty_kind = c.LLVMGetTypeKind(ty); - if (ty_kind == c.LLVMIntegerTypeKind) { - // Plain enum or builtin integer → integer constant - self.mapRef(c.LLVMConstInt(ty, ei.tag, 0)); - } else if (ty_kind == c.LLVMStructTypeKind) { - // Tagged union with no payload — header field 0 holds the tag - const header_ty = c.LLVMStructGetTypeAtIndex(ty, 0); - const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); - var result = c.LLVMGetUndef(ty); - result = c.LLVMBuildInsertValue(self.builder, result, tag_val, 0, "ei.tag"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMConstInt(self.cached_i64, ei.tag, 0)); - } - } else { - // Tagged union with payload — { header, payload_bytes } - const union_ty = self.toLLVMType(instruction.ty); - const header_ty = c.LLVMStructGetTypeAtIndex(union_ty, 0); - const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); - const payload_val = self.resolveRef(ei.payload); - - // alloca union, store tag, bitcast payload area, store payload - const tmp = c.LLVMBuildAlloca(self.builder, union_ty, "ei.tmp"); - // Store tag at field 0 - const tag_ptr = c.LLVMBuildStructGEP2(self.builder, union_ty, tmp, 0, "ei.tagp"); - _ = c.LLVMBuildStore(self.builder, tag_val, tag_ptr); - // Store payload at field 1 (bitcast the byte array to payload type) - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_ty, tmp, 1, "ei.pp"); - const payload_typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ei.pcast"); - _ = c.LLVMBuildStore(self.builder, payload_val, payload_typed_ptr); - // Load the whole union value - self.mapRef(c.LLVMBuildLoad2(self.builder, union_ty, tmp, "ei.val")); - } - }, - .enum_tag => |un| { - const val = self.resolveRef(un.operand); - // Check if this is a plain enum (integer) or tagged union (struct with tag at 0) - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - // Tagged union — extract field 0 (tag) - var tag = c.LLVMBuildExtractValue(self.builder, val, 0, "etag"); - // Truncate to declared tag width if needed (e.g. i64 → i32 for u32 tags) - // This is essential for FFI unions where the i64 tag slot contains - // a smaller tag + uninitialized padding (e.g. SDL_Event's u32 type + u32 reserved) - const target_ty = self.toLLVMType(instruction.ty); - const extracted_bits = c.LLVMGetIntTypeWidth(c.LLVMTypeOf(tag)); - const target_bits = c.LLVMGetIntTypeWidth(target_ty); - if (target_bits < extracted_bits) { - tag = c.LLVMBuildTrunc(self.builder, tag, target_ty, "etag.trunc"); - } - self.mapRef(tag); - } else { - // Plain enum — the value IS the tag - self.mapRef(val); - } - }, - .enum_payload => |fa| { - const base = self.resolveRef(fa.base); - const result_ty = self.toLLVMType(instruction.ty); - const base_ty = c.LLVMTypeOf(base); - const base_kind = c.LLVMGetTypeKind(base_ty); - if (base_kind == c.LLVMStructTypeKind) { - // Tagged union: alloca, store, GEP field 1 (payload area), bitcast, load - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ep.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ep.pp"); - const typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ep.cast"); - self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, typed_ptr, "ep.val")); - } else { - self.mapRef(c.LLVMGetUndef(result_ty)); - } - }, + .enum_init => |ei| self.ops().emitEnumInit(instruction, ei), + .enum_tag => |un| self.ops().emitEnumTag(instruction, un), + .enum_payload => |fa| self.ops().emitEnumPayload(instruction, fa), // ── Union ops ──────────────────────────────────────────── - .union_get => |fa| { - const base = self.resolveRef(fa.base); - const result_ty = self.toLLVMType(instruction.ty); - // Union field access: reinterpret the union's data area as the target type - const base_ty = c.LLVMTypeOf(base); - const kind = c.LLVMGetTypeKind(base_ty); - if (kind == c.LLVMStructTypeKind) { - // Tagged union { header, payload_bytes } — access payload at field 1 - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ug.pp"); - self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, payload_ptr, "ug.val")); - } else { - // Untagged union [N x i8] — alloca, store, reinterpret-load - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, tmp, "ug.val")); - } - }, - .union_gep => |fa| { - const base_ptr = self.resolveRef(fa.base); - const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); - if (base_ty_kind == c.LLVMPointerTypeKind) { - const union_llvm_ty = if (fa.base_type) |bt| - self.toLLVMType(self.resolveAggregate(bt)) - else - self.resolveGepStructType(fa.base, instruction); - const st_kind = c.LLVMGetTypeKind(union_llvm_ty); - if (st_kind == c.LLVMStructTypeKind) { - // Tagged union — payload is at field 1 - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_llvm_ty, base_ptr, 1, "ugep.pp"); - self.mapRef(payload_ptr); - } else { - // Untagged union — data starts at offset 0 - self.mapRef(base_ptr); - } - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, + .union_get => |fa| self.ops().emitUnionGet(instruction, fa), + .union_gep => |fa| self.ops().emitUnionGep(instruction, fa), // ── Array/Slice ops ─────────────────────────────────────── - .index_get => |bin| { - const base = self.resolveRef(bin.lhs); - const idx = self.resolveRef(bin.rhs); - const base_ty = c.LLVMTypeOf(base); - const kind = c.LLVMGetTypeKind(base_ty); - if (kind == c.LLVMVectorTypeKind or kind == c.LLVMScalableVectorTypeKind) { - // Vector — use extractelement - // Coerce index to i32 if needed - const idx32 = self.coerceArg(idx, self.cached_i32); - self.mapRef(c.LLVMBuildExtractElement(self.builder, base, idx32, "ve")); - } else if (kind == c.LLVMArrayTypeKind) { - // Fixed-size array value — alloca, store, GEP, load - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ig.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - const elem_ty = self.toLLVMType(instruction.ty); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), idx }; - const ptr = c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "ig.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val")); - } else if (kind == c.LLVMPointerTypeKind) { - // Pointer (many-pointer or raw ptr) — GEP + load - const elem_ty = self.toLLVMType(instruction.ty); - var indices = [_]c.LLVMValueRef{idx}; - const ptr = c.LLVMBuildGEP2(self.builder, elem_ty, base, &indices, 1, "ig.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val")); - } else if (kind == c.LLVMStructTypeKind) { - // Slice/string {ptr, len} — extract ptr, GEP, load - const data = c.LLVMBuildExtractValue(self.builder, base, 0, "ig.data"); - const elem_ty = self.toLLVMType(instruction.ty); - var indices = [_]c.LLVMValueRef{idx}; - const ptr = c.LLVMBuildGEP2(self.builder, elem_ty, data, &indices, 1, "ig.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val")); - } else { - // Non-aggregate base (lowering error) — emit undef - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } - }, - .index_gep => |bin| { - const base = self.resolveRef(bin.lhs); - const idx = self.resolveRef(bin.rhs); - const base_ty = c.LLVMTypeOf(base); - const kind = c.LLVMGetTypeKind(base_ty); - if (kind == c.LLVMArrayTypeKind) { - // Fixed-size array value — alloca, store, GEP - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "igp.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), idx }; - self.mapRef(c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "igp.ptr")); - } else if (kind == c.LLVMPointerTypeKind) { - // Pointer — GEP with proper element type - const gep_elem = blk: { - // instruction.ty is the result type (ptr to element) - // Resolve the pointee type for the GEP element size - const info = self.ir_mod.types.get(instruction.ty); - break :blk switch (info) { - .pointer => |p| self.toLLVMType(p.pointee), - .many_pointer => |p| self.toLLVMType(p.element), - else => self.cached_i8, // fallback - }; - }; - var indices = [_]c.LLVMValueRef{idx}; - self.mapRef(c.LLVMBuildGEP2(self.builder, gep_elem, base, &indices, 1, "igp.ptr")); - } else if (kind == c.LLVMStructTypeKind) { - // Slice/string {ptr, len} — extract ptr, GEP with proper element type - const data = c.LLVMBuildExtractValue(self.builder, base, 0, "igp.data"); - const gep_elem = blk: { - const info = self.ir_mod.types.get(instruction.ty); - break :blk switch (info) { - .pointer => |p| self.toLLVMType(p.pointee), - .many_pointer => |p| self.toLLVMType(p.element), - else => self.cached_i8, - }; - }; - var indices = [_]c.LLVMValueRef{idx}; - self.mapRef(c.LLVMBuildGEP2(self.builder, gep_elem, data, &indices, 1, "igp.ptr")); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, - .length => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMArrayTypeKind) { - const len = c.LLVMGetArrayLength2(val_ty); - self.mapRef(c.LLVMConstInt(self.cached_i64, len, 0)); - } else if (kind == c.LLVMStructTypeKind) { - // Slice/string {ptr, len} — extract field 1 (len) - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 1, "len")); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_i64)); - } - }, - .data_ptr => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "dptr")); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, - .subslice => |ss| { - const base = self.resolveRef(ss.base); - var lo = self.resolveRef(ss.lo); - var hi = self.resolveRef(ss.hi); - // Normalize lo/hi to i64 for consistent arithmetic (indices are unsigned) - if (c.LLVMTypeOf(lo) != self.cached_i64) { - lo = c.LLVMBuildZExt(self.builder, lo, self.cached_i64, "ss.lo64"); - } - if (c.LLVMTypeOf(hi) != self.cached_i64) { - hi = c.LLVMBuildZExt(self.builder, hi, self.cached_i64, "ss.hi64"); - } - const base_ty = c.LLVMTypeOf(base); - const base_kind = c.LLVMGetTypeKind(base_ty); - const slice_ty = self.toLLVMType(instruction.ty); - // Resolve element type from the result slice type for correct GEP stride - const elem_ty = blk: { - const info = self.ir_mod.types.get(instruction.ty); - break :blk switch (info) { - .slice => |s| self.toLLVMType(s.element), - else => self.cached_i8, - }; - }; - if (base_kind == c.LLVMStructTypeKind) { - // Slice/string: extract data ptr, GEP by lo - const data = c.LLVMBuildExtractValue(self.builder, base, 0, "ss.data"); - var lo_indices = [_]c.LLVMValueRef{lo}; - const new_ptr = c.LLVMBuildGEP2(self.builder, elem_ty, data, &lo_indices, 1, "ss.ptr"); - var new_len = c.LLVMBuildSub(self.builder, hi, lo, "ss.len"); - // Ensure length is i64 for slice struct {ptr, i64} - if (c.LLVMTypeOf(new_len) != self.cached_i64) { - new_len = c.LLVMBuildSExt(self.builder, new_len, self.cached_i64, "ss.ext"); - } - var result = c.LLVMGetUndef(slice_ty); - result = c.LLVMBuildInsertValue(self.builder, result, new_ptr, 0, "ss.wptr"); - result = c.LLVMBuildInsertValue(self.builder, result, new_len, 1, "ss.wlen"); - self.mapRef(result); - } else if (base_kind == c.LLVMArrayTypeKind) { - // Array: alloca, GEP to element at lo, compute len - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ss.arr"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), lo }; - const new_ptr = c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "ss.ptr"); - var new_len = c.LLVMBuildSub(self.builder, hi, lo, "ss.len"); - // Ensure length is i64 for slice struct {ptr, i64} - if (c.LLVMTypeOf(new_len) != self.cached_i64) { - new_len = c.LLVMBuildSExt(self.builder, new_len, self.cached_i64, "ss.ext"); - } - var result = c.LLVMGetUndef(slice_ty); - result = c.LLVMBuildInsertValue(self.builder, result, new_ptr, 0, "ss.wptr"); - result = c.LLVMBuildInsertValue(self.builder, result, new_len, 1, "ss.wlen"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMGetUndef(slice_ty)); - } - }, - .array_to_slice => |un| { - const arr = self.resolveRef(un.operand); - const arr_ty = c.LLVMTypeOf(arr); - const arr_kind = c.LLVMGetTypeKind(arr_ty); - if (arr_kind == c.LLVMArrayTypeKind) { - const len = c.LLVMGetArrayLength2(arr_ty); - const tmp = c.LLVMBuildAlloca(self.builder, arr_ty, "a2s.tmp"); - _ = c.LLVMBuildStore(self.builder, arr, tmp); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), c.LLVMConstInt(self.cached_i64, 0, 0) }; - const elem_ptr = c.LLVMBuildGEP2(self.builder, arr_ty, tmp, &indices, 2, "a2s.ptr"); - const slice_ty = self.toLLVMType(instruction.ty); - var result = c.LLVMGetUndef(slice_ty); - result = c.LLVMBuildInsertValue(self.builder, result, elem_ptr, 0, "a2s.wptr"); - const len_val = c.LLVMConstInt(self.cached_i64, len, 0); - result = c.LLVMBuildInsertValue(self.builder, result, len_val, 1, "a2s.wlen"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } - }, + .index_get => |bin| self.ops().emitIndexGet(instruction, bin), + .index_gep => |bin| self.ops().emitIndexGep(instruction, bin), + .length => |un| self.ops().emitLength(un), + .data_ptr => |un| self.ops().emitDataPtr(un), + .subslice => |ss| self.ops().emitSubslice(instruction, ss), + .array_to_slice => |un| self.ops().emitArrayToSlice(instruction, un), // ── Call extensions ─────────────────────────────────────── .call_builtin => |bi| self.ops().emitCallBuiltin(instruction, bi), @@ -1923,123 +1539,14 @@ pub const LLVMEmitter = struct { .call_closure => |call_op| self.ops().emitCallClosure(instruction, call_op), // ── Tuple ops ──────────────────────────────────────────── - .tuple_init => |agg| { - const tuple_ty = self.toLLVMType(instruction.ty); - var result = c.LLVMGetUndef(tuple_ty); - for (agg.fields, 0..) |field_ref, i| { - const field_val = self.resolveRef(field_ref); - result = c.LLVMBuildInsertValue(self.builder, result, field_val, @intCast(i), "ti"); - } - self.mapRef(result); - }, - .tuple_get => |fa| { - const base = self.resolveRef(fa.base); - self.mapRef(c.LLVMBuildExtractValue(self.builder, base, @intCast(fa.field_index), "tg")); - }, + .tuple_init => |agg| self.ops().emitTupleInit(instruction, agg), + .tuple_get => |fa| self.ops().emitTupleGet(fa), // ── Optional ops ───────────────────────────────────────── - .optional_wrap => |un| { - var val = self.resolveRef(un.operand); - const opt_ty = self.toLLVMType(instruction.ty); - const opt_kind = c.LLVMGetTypeKind(opt_ty); - if (opt_kind == c.LLVMPointerTypeKind) { - // ?*T — pointer is the optional itself (null = none) - self.mapRef(val); - } else if (opt_kind == c.LLVMStructTypeKind) { - // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) - const num_fields = c.LLVMCountStructElementTypes(opt_ty); - const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(opt_ty, num_fields - 1) else self.cached_i1; - if (last_field_ty == self.cached_i1) { - // ?T → { T, i1 } — wrap value + true flag - const inner_ty = c.LLVMStructGetTypeAtIndex(opt_ty, 0); - val = self.coerceArg(val, inner_ty); - var result = c.LLVMGetUndef(opt_ty); - result = c.LLVMBuildInsertValue(self.builder, result, val, 0, "ow.val"); - result = c.LLVMBuildInsertValue(self.builder, result, c.LLVMConstInt(self.cached_i1, 1, 0), 1, "ow.has"); - self.mapRef(result); - } else { - // ?Closure → closure struct IS the optional, just pass through - self.mapRef(val); - } - } else { - self.mapRef(val); - } - }, - .optional_unwrap => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) - const num_fields = c.LLVMCountStructElementTypes(val_ty); - const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.cached_i1; - if (last_field_ty == self.cached_i1) { - // { T, i1 } → extract field 0 - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "ou.val")); - } else { - // ?Closure → the struct itself is the value - self.mapRef(val); - } - } else { - // ?*T → pointer is the value itself - self.mapRef(val); - } - }, - .optional_has_value => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) - const num_fields = c.LLVMCountStructElementTypes(val_ty); - const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.cached_i1; - if (last_field_ty == self.cached_i1) { - // { T, i1 } → extract has_value flag - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, num_fields - 1, "oh.has")); - } else { - // ?Closure {fn_ptr, env} → check if fn_ptr is null - const fn_ptr = c.LLVMBuildExtractValue(self.builder, val, 0, "oh.fn"); - self.mapRef(c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oh.nn")); - } - } else { - // ?*T → compare with null - const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, val, c.LLVMConstNull(val_ty), "oh.nn"); - self.mapRef(is_nonnull); - } - }, - .optional_coalesce => |bin| { - // a ?? b — if a has value, use a's value; otherwise use b - const a = self.resolveRef(bin.lhs); - var b_val = self.resolveRef(bin.rhs); - const a_ty = c.LLVMTypeOf(a); - const kind = c.LLVMGetTypeKind(a_ty); - if (kind == c.LLVMStructTypeKind) { - const n_fields = c.LLVMCountStructElementTypes(a_ty); - const f1_ty = if (n_fields >= 2) c.LLVMStructGetTypeAtIndex(a_ty, 1) else null; - const is_ti1 = if (f1_ty) |ft| c.LLVMGetTypeKind(ft) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(ft) == 1 else false; - if (is_ti1) { - // Standard optional {T, i1}: extract has_value and unwrap - const has = c.LLVMBuildExtractValue(self.builder, a, 1, "oc.has"); - const unwrapped = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.val"); - const uw_ty = c.LLVMTypeOf(unwrapped); - const b_ty = c.LLVMTypeOf(b_val); - if (uw_ty != b_ty) { - b_val = self.coerceArg(b_val, uw_ty); - } - self.mapRef(c.LLVMBuildSelect(self.builder, has, unwrapped, b_val, "oc.sel")); - } else { - // ?Closure {fn_ptr, env}: check if fn_ptr is null - const fn_ptr = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.fn"); - const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oc.nn"); - // Select the full closure struct, not just the fn_ptr - self.mapRef(c.LLVMBuildSelect(self.builder, is_nonnull, a, b_val, "oc.sel")); - } - } else { - // ?*T — select on null - const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, a, c.LLVMConstNull(a_ty), "oc.nn"); - self.mapRef(c.LLVMBuildSelect(self.builder, is_nonnull, a, b_val, "oc.sel")); - } - }, + .optional_wrap => |un| self.ops().emitOptionalWrap(instruction, un), + .optional_unwrap => |un| self.ops().emitOptionalUnwrap(un), + .optional_has_value => |un| self.ops().emitOptionalHasValue(un), + .optional_coalesce => |bin| self.ops().emitOptionalCoalesce(bin), // ── Box/Unbox Any ──────────────────────────────────────── .box_any => |ba| { @@ -2253,7 +1760,7 @@ pub const LLVMEmitter = struct { /// Resolve the struct LLVM type for GEP operations. /// Uses LLVM alloca type when available, falls back to IR type system. - fn resolveGepStructType(self: *LLVMEmitter, base_ref: Ref, instruction: *const Inst) c.LLVMTypeRef { + pub fn resolveGepStructType(self: *LLVMEmitter, base_ref: Ref, instruction: *const Inst) c.LLVMTypeRef { const base_val = self.resolveRef(base_ref); // Strategy 1: base is an alloca — get allocated type directly @@ -2296,7 +1803,7 @@ pub const LLVMEmitter = struct { } /// Resolve through pointer types to get the underlying aggregate type. - fn resolveAggregate(self: *LLVMEmitter, ty: TypeId) TypeId { + pub fn resolveAggregate(self: *LLVMEmitter, ty: TypeId) TypeId { if (!ty.isBuiltin()) { const info = self.ir_mod.types.get(ty); if (info == .pointer) return info.pointer.pointee; From 0e7bae563a449545690ad0457b2ff6dfa4058862 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 12:41:39 +0300 Subject: [PATCH 14/26] refactor(backend): drain remaining emitInst handlers into ops.zig (A7.4 slice e) Move the final inline emitInst handler groups (terminators, box/unbox-Any, reflection, switch-branch, closure-creation, vector, block-param, misc) into the Ops facade in src/backend/llvm/ops.zig. emitInst is now pure dispatch: every arm delegates to self.ops().*, leaving only setInstDebugLocation plus one-line delegations. Widen the shared infra the moved bodies reach (emitFailableMainRet, getBlock, anyTag, isSignedTypeEx, coerceToI64/coerceToI64Signed/coerceFromI64, emitFieldValueGet) to pub on LLVMEmitter; helper and ref-tracking sections stay put. Pure relocation: emitted LLVM IR byte-identical, zero snapshot churn. --- src/backend/llvm/ops.zig | 273 +++++++++++++++++++++++++++++++++++++-- src/ir/emit_llvm.zig | 250 ++++------------------------------- 2 files changed, 288 insertions(+), 235 deletions(-) diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index ee5e639..e34bbb1 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -25,21 +25,32 @@ const CallIndirect = ir_inst.CallIndirect; const ObjcMsgSend = ir_inst.ObjcMsgSend; const JniMsgSend = ir_inst.JniMsgSend; const BuiltinCall = ir_inst.BuiltinCall; +const TriOp = ir_inst.TriOp; +const Branch = ir_inst.Branch; +const CondBranch = ir_inst.CondBranch; +const SwitchBranch = ir_inst.SwitchBranch; +const BoxAny = ir_inst.BoxAny; +const ClosureCreate = ir_inst.ClosureCreate; +const BlockParam = ir_inst.BlockParam; +const FieldReflect = ir_inst.FieldReflect; const TypeId = ir_types.TypeId; const StringId = ir_types.StringId; -/// Instruction-emission handlers for `emitInst`: the constant, arithmetic, -/// bitwise, comparison, logical, memory, globals, conversion, pointer, and -/// call opcodes (direct/indirect/objc/jni dispatch plus builtin, compiler, -/// and closure calls), plus the aggregate ops (struct, enum, union, -/// array/slice, tuple, and optional). A backend `*LLVMEmitter` facade -/// (field `e`): each method emits one opcode's LLVM IR via `self.e.*`. The -/// shared infra these bodies call back into (`mapRef`/`resolveRef`/ -/// `matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/`emitStrCmp`/ +/// Instruction-emission handlers for `emitInst`: every opcode group — the +/// constant, arithmetic, bitwise, comparison, logical, memory, globals, +/// conversion, pointer, and call opcodes (direct/indirect/objc/jni dispatch +/// plus builtin, compiler, and closure calls), the aggregate ops (struct, +/// enum, union, array/slice, tuple, and optional), and the terminators, +/// box/unbox-Any, reflection, switch-branch, closure-creation, vector, +/// block-param, and misc ops. A backend `*LLVMEmitter` facade (field `e`): +/// each method emits one opcode's LLVM IR via `self.e.*`. The shared infra +/// these bodies call back into (`mapRef`/`resolveRef`/`advanceRefCounter`/ +/// `getBlock`/`matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/`emitStrCmp`/ /// `emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/ /// `getRefIRType`/`loadJniFn`/`extractSlicePtr`/`emitJniConstructor`/ -/// `resolveAggregate`/`resolveGepStructType`) stays on `LLVMEmitter`. -/// `emitInst`'s arms reach these via `self.ops()`. +/// `emitFailableMainRet`/`emitFieldValueGet`/`resolveAggregate`/ +/// `resolveGepStructType`) stays on `LLVMEmitter`. `emitInst`'s arms reach +/// these via `self.ops()`. pub const Ops = struct { e: *LLVMEmitter, @@ -1689,4 +1700,246 @@ pub const Ops = struct { self.e.mapRef(c.LLVMBuildSelect(self.e.builder, is_nonnull, a, b_val, "oc.sel")); } } + + // ── Terminators ──────────────────────────────────────── + pub fn emitRet(self: Ops, un: UnaryOp) void { + var val = self.e.resolveRef(un.operand); + const func = &self.e.ir_mod.functions.items[self.e.current_func_idx]; + // Failable main: wrap the return in the entry-point reporter + // (ERR E4.2) — exit 0 (or the value) on success, else print the + // trace + tag to stderr and exit 1 — instead of returning the + // tag/tuple as the raw exit code. Two shapes: + // `-> !` → `val` is the bare u32 error tag. + // `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both. + if (self.e.current_func_is_main) { + const rinfo = self.e.ir_mod.types.get(func.ret); + if (rinfo == .error_set) { + self.e.emitFailableMainRet(null, val); + self.e.advanceRefCounter(); + return; + } + if (rinfo == .tuple and rinfo.tuple.fields.len == 2 and + self.e.ir_mod.types.get(rinfo.tuple.fields[1]) == .error_set) + { + const value = c.LLVMBuildExtractValue(self.e.builder, val, 0, "main.ret.val"); + const tag = c.LLVMBuildExtractValue(self.e.builder, val, 1, "main.ret.tag"); + self.e.emitFailableMainRet(value, tag); + self.e.advanceRefCounter(); + return; + } + } + // sret-shaped function: declared return-type-in-IR is + // the struct, but the LLVM signature is void with a + // prepended ptr sret param. Store the value through + // the sret slot and emit ret void. + const needs_c_abi = func.is_extern or func.call_conv == .c; + const raw_ret = self.e.toLLVMType(func.ret); + if (needs_c_abi and self.e.needsByval(func.ret, raw_ret)) { + const llvm_func2 = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder)); + const sret_ptr = c.LLVMGetParam(llvm_func2, 0); + _ = c.LLVMBuildStore(self.e.builder, val, sret_ptr); + _ = c.LLVMBuildRetVoid(self.e.builder); + self.e.advanceRefCounter(); + return; + } + // Coerce return value to match the function's LLVM return type + const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder)); + const fn_ty = c.LLVMGlobalGetValueType(llvm_func); + const expected_ret = c.LLVMGetReturnType(fn_ty); + val = self.e.coerceArg(val, expected_ret); + // If coercion didn't fix the type (e.g. dead comptime function), + // emit undef of the correct type to avoid LLVM verification error + if (c.LLVMTypeOf(val) != expected_ret) { + val = c.LLVMGetUndef(expected_ret); + } + _ = c.LLVMBuildRet(self.e.builder, val); + self.e.advanceRefCounter(); + } + + pub fn emitRetVoid(self: Ops) void { + if (self.e.current_func_is_main) { + // main must return i32 0 for JIT + _ = c.LLVMBuildRet(self.e.builder, c.LLVMConstInt(self.e.cached_i32, 0, 0)); + } else { + _ = c.LLVMBuildRetVoid(self.e.builder); + } + self.e.advanceRefCounter(); + } + + pub fn emitUnreachable(self: Ops) void { + _ = c.LLVMBuildUnreachable(self.e.builder); + self.e.advanceRefCounter(); + } + + pub fn emitBr(self: Ops, branch: Branch, func_idx: u32) void { + const target = self.e.getBlock(func_idx, branch.target); + _ = c.LLVMBuildBr(self.e.builder, target); + self.e.advanceRefCounter(); + } + + pub fn emitCondBr(self: Ops, cbr: CondBranch, func_idx: u32) void { + var cond = self.e.resolveRef(cbr.cond); + const then_bb = self.e.getBlock(func_idx, cbr.then_target); + const else_bb = self.e.getBlock(func_idx, cbr.else_target); + // Coerce condition to i1 if needed (e.g., loaded bool stored as i64) + const cond_ty = c.LLVMTypeOf(cond); + const cond_kind = c.LLVMGetTypeKind(cond_ty); + if (cond_ty != self.e.cached_i1) { + if (cond_kind == c.LLVMPointerTypeKind) { + cond = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, cond, c.LLVMConstNull(cond_ty), "tobool"); + } else if (cond_kind == c.LLVMIntegerTypeKind) { + cond = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, cond, c.LLVMConstInt(cond_ty, 0, 0), "tobool"); + } else if (cond_kind == c.LLVMStructTypeKind) { + // Struct values are always truthy + cond = c.LLVMConstInt(self.e.cached_i1, 1, 0); + } else { + cond = c.LLVMConstInt(self.e.cached_i1, 1, 0); // default truthy + } + } + _ = c.LLVMBuildCondBr(self.e.builder, cond, then_bb, else_bb); + self.e.advanceRefCounter(); + } + + // ── Box/Unbox Any ────────────────────────────────────── + pub fn emitBoxAny(self: Ops, ba: BoxAny) void { + const val = self.e.resolveRef(ba.operand); + const any_ty = self.e.getAnyStructType(); + // Any = { type_tag: i64, value: i64 } + const tag = c.LLVMConstInt(self.e.cached_i64, self.e.anyTag(ba.source_type), 0); + // Bitcast value to i64, using SExt for signed types, ZExt otherwise + const is_signed = self.e.isSignedTypeEx(ba.source_type); + const val_as_i64 = if (is_signed) self.e.coerceToI64Signed(val) else self.e.coerceToI64(val); + var result = c.LLVMGetUndef(any_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, tag, 0, "ba.tag"); + result = c.LLVMBuildInsertValue(self.e.builder, result, val_as_i64, 1, "ba.val"); + self.e.mapRef(result); + } + + pub fn emitUnboxAny(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const any_val = self.e.resolveRef(un.operand); + const any_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(any_val)); + if (any_kind == c.LLVMStructTypeKind) { + const raw = c.LLVMBuildExtractValue(self.e.builder, any_val, 1, "ua.raw"); + const target_ty = self.e.toLLVMType(instruction.ty); + self.e.mapRef(self.e.coerceFromI64(raw, target_ty)); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + + // ── Reflection ops ───────────────────────────────────── + pub fn emitFieldNameGet(self: Ops, fr: FieldReflect) void { + // Build global string array for this struct's field names, then GEP at runtime index + const global = self.e.reflection().getOrBuildFieldNameArray(fr.struct_type); + const idx = self.e.resolveRef(fr.index); + const string_ty = self.e.getStringStructType(); + // Get struct field count for array type + const field_info = self.e.ir_mod.types.get(fr.struct_type); + const field_count: u32 = switch (field_info) { + .@"struct" => |s| @intCast(s.fields.len), + .@"union" => |u| @intCast(u.fields.len), + .tagged_union => |u| @intCast(u.fields.len), + .@"enum" => |e| @intCast(e.variants.len), + else => 0, + }; + const array_ty = c.LLVMArrayType(string_ty, field_count); + const zero = c.LLVMConstInt(self.e.cached_i64, 0, 0); + var indices = [2]c.LLVMValueRef{ zero, idx }; + const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, array_ty, global, &indices, 2, "fn.gep"); + const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "fn.load"); + self.e.mapRef(result); + } + + pub fn emitFieldValueGet(self: Ops, fr: FieldReflect, func_idx: u32) void { + // Switch on index, each case: extractvalue field k → box as Any + self.e.emitFieldValueGet(fr, func_idx); + } + + pub fn emitErrorTagNameGet(self: Ops, u: UnaryOp) void { + // Tag id → name: GEP into the always-linked tag-name table at + // the runtime tag id (the error-set value, a u32). Out-of-range + // ids can't occur — ids come from the same registry the table + // is built from — so no bounds branch is needed. + const global = self.e.reflection().getOrBuildTagNameArray(); + const tag_raw = self.e.resolveRef(u.operand); + const idx = c.LLVMBuildZExt(self.e.builder, tag_raw, self.e.cached_i64, "etn.idx"); + const string_ty = self.e.getStringStructType(); + const n: u32 = @intCast(self.e.ir_mod.types.tags.names.items.len); + const array_ty = c.LLVMArrayType(string_ty, n); + const zero = c.LLVMConstInt(self.e.cached_i64, 0, 0); + var indices = [2]c.LLVMValueRef{ zero, idx }; + const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, array_ty, global, &indices, 2, "etn.gep"); + const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "etn.load"); + self.e.mapRef(result); + } + + // ── Switch branch ────────────────────────────────────── + pub fn emitSwitchBr(self: Ops, sw: SwitchBranch, func_idx: u32) void { + const operand = self.e.resolveRef(sw.operand); + const default_bb = self.e.getBlock(func_idx, sw.default); + const switch_inst = c.LLVMBuildSwitch(self.e.builder, operand, default_bb, @intCast(sw.cases.len)); + for (sw.cases) |case| { + const case_val = c.LLVMConstInt(c.LLVMTypeOf(operand), @bitCast(case.value), 0); + const case_bb = self.e.getBlock(func_idx, case.target); + c.LLVMAddCase(switch_inst, case_val, case_bb); + } + self.e.advanceRefCounter(); + } + + // ── Closure creation ─────────────────────────────────── + pub fn emitClosureCreate(self: Ops, cc: ClosureCreate) void { + const fn_val = self.e.func_map.get(cc.func.index()) orelse c.LLVMGetUndef(self.e.cached_ptr); + const env_val = if (cc.env.isNone()) c.LLVMConstNull(self.e.cached_ptr) else self.e.resolveRef(cc.env); + const closure_ty = self.e.getClosureStructType(); + var result = c.LLVMGetUndef(closure_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, fn_val, 0, "cc.fn"); + result = c.LLVMBuildInsertValue(self.e.builder, result, env_val, 1, "cc.env"); + self.e.mapRef(result); + } + + // ── Vector ops ───────────────────────────────────────── + pub fn emitVecSplat(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const scalar = self.e.resolveRef(un.operand); + const vec_ty = self.e.toLLVMType(instruction.ty); + const vec_len = c.LLVMGetVectorSize(vec_ty); + // Build a splat: insertelement into undef for each lane + var result = c.LLVMGetUndef(vec_ty); + var i: c_uint = 0; + while (i < vec_len) : (i += 1) { + const idx_val = c.LLVMConstInt(self.e.cached_i32, i, 0); + result = c.LLVMBuildInsertElement(self.e.builder, result, scalar, idx_val, "splat"); + } + self.e.mapRef(result); + } + + pub fn emitVecExtract(self: Ops, bin: BinOp) void { + const vec = self.e.resolveRef(bin.lhs); + const idx = self.e.resolveRef(bin.rhs); + self.e.mapRef(c.LLVMBuildExtractElement(self.e.builder, vec, idx, "vext")); + } + + pub fn emitVecInsert(self: Ops, tri: TriOp) void { + const vec = self.e.resolveRef(tri.a); + const idx = self.e.resolveRef(tri.b); + const val = self.e.resolveRef(tri.c); + self.e.mapRef(c.LLVMBuildInsertElement(self.e.builder, vec, val, idx, "vins")); + } + + // ── Block params ─────────────────────────────────────── + pub fn emitBlockParam(self: Ops, instruction: *const Inst, bp: BlockParam) void { + // Create a PHI node — incoming values are filled in by fixupPhiNodes + const ty = self.e.toLLVMType(instruction.ty); + const phi = c.LLVMBuildPhi(self.e.builder, ty, "bp"); + self.e.pending_phis.append(self.e.alloc, .{ + .phi = phi, + .block_id = bp.block, + .param_index = bp.param_index, + }) catch unreachable; + self.e.mapRef(phi); + } + + // ── Misc ─────────────────────────────────────────────── + pub fn emitPlaceholder(self: Ops, instruction: *const Inst) void { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 5a9af27..78853d2 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1417,99 +1417,11 @@ pub const LLVMEmitter = struct { .call_indirect => |call_op| self.ops().emitCallIndirect(instruction, call_op), // ── Terminators ──────────────────────────────────────── - .ret => |un| { - var val = self.resolveRef(un.operand); - const func = &self.ir_mod.functions.items[self.current_func_idx]; - // Failable main: wrap the return in the entry-point reporter - // (ERR E4.2) — exit 0 (or the value) on success, else print the - // trace + tag to stderr and exit 1 — instead of returning the - // tag/tuple as the raw exit code. Two shapes: - // `-> !` → `val` is the bare u32 error tag. - // `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both. - if (self.current_func_is_main) { - const rinfo = self.ir_mod.types.get(func.ret); - if (rinfo == .error_set) { - self.emitFailableMainRet(null, val); - self.advanceRefCounter(); - return; - } - if (rinfo == .tuple and rinfo.tuple.fields.len == 2 and - self.ir_mod.types.get(rinfo.tuple.fields[1]) == .error_set) - { - const value = c.LLVMBuildExtractValue(self.builder, val, 0, "main.ret.val"); - const tag = c.LLVMBuildExtractValue(self.builder, val, 1, "main.ret.tag"); - self.emitFailableMainRet(value, tag); - self.advanceRefCounter(); - return; - } - } - // sret-shaped function: declared return-type-in-IR is - // the struct, but the LLVM signature is void with a - // prepended ptr sret param. Store the value through - // the sret slot and emit ret void. - const needs_c_abi = func.is_extern or func.call_conv == .c; - const raw_ret = self.toLLVMType(func.ret); - if (needs_c_abi and self.needsByval(func.ret, raw_ret)) { - const llvm_func2 = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder)); - const sret_ptr = c.LLVMGetParam(llvm_func2, 0); - _ = c.LLVMBuildStore(self.builder, val, sret_ptr); - _ = c.LLVMBuildRetVoid(self.builder); - self.advanceRefCounter(); - return; - } - // Coerce return value to match the function's LLVM return type - const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder)); - const fn_ty = c.LLVMGlobalGetValueType(llvm_func); - const expected_ret = c.LLVMGetReturnType(fn_ty); - val = self.coerceArg(val, expected_ret); - // If coercion didn't fix the type (e.g. dead comptime function), - // emit undef of the correct type to avoid LLVM verification error - if (c.LLVMTypeOf(val) != expected_ret) { - val = c.LLVMGetUndef(expected_ret); - } - _ = c.LLVMBuildRet(self.builder, val); - self.advanceRefCounter(); - }, - .ret_void => { - if (self.current_func_is_main) { - // main must return i32 0 for JIT - _ = c.LLVMBuildRet(self.builder, c.LLVMConstInt(self.cached_i32, 0, 0)); - } else { - _ = c.LLVMBuildRetVoid(self.builder); - } - self.advanceRefCounter(); - }, - .@"unreachable" => { - _ = c.LLVMBuildUnreachable(self.builder); - self.advanceRefCounter(); - }, - .br => |branch| { - const target = self.getBlock(func_idx, branch.target); - _ = c.LLVMBuildBr(self.builder, target); - self.advanceRefCounter(); - }, - .cond_br => |cbr| { - var cond = self.resolveRef(cbr.cond); - const then_bb = self.getBlock(func_idx, cbr.then_target); - const else_bb = self.getBlock(func_idx, cbr.else_target); - // Coerce condition to i1 if needed (e.g., loaded bool stored as i64) - const cond_ty = c.LLVMTypeOf(cond); - const cond_kind = c.LLVMGetTypeKind(cond_ty); - if (cond_ty != self.cached_i1) { - if (cond_kind == c.LLVMPointerTypeKind) { - cond = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond, c.LLVMConstNull(cond_ty), "tobool"); - } else if (cond_kind == c.LLVMIntegerTypeKind) { - cond = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond, c.LLVMConstInt(cond_ty, 0, 0), "tobool"); - } else if (cond_kind == c.LLVMStructTypeKind) { - // Struct values are always truthy - cond = c.LLVMConstInt(self.cached_i1, 1, 0); - } else { - cond = c.LLVMConstInt(self.cached_i1, 1, 0); // default truthy - } - } - _ = c.LLVMBuildCondBr(self.builder, cond, then_bb, else_bb); - self.advanceRefCounter(); - }, + .ret => |un| self.ops().emitRet(un), + .ret_void => self.ops().emitRetVoid(), + .@"unreachable" => self.ops().emitUnreachable(), + .br => |branch| self.ops().emitBr(branch, func_idx), + .cond_br => |cbr| self.ops().emitCondBr(cbr, func_idx), // ── Struct ops ──────────────────────────────────────────── .struct_init => |agg| self.ops().emitStructInit(instruction, agg), @@ -1549,142 +1461,30 @@ pub const LLVMEmitter = struct { .optional_coalesce => |bin| self.ops().emitOptionalCoalesce(bin), // ── Box/Unbox Any ──────────────────────────────────────── - .box_any => |ba| { - const val = self.resolveRef(ba.operand); - const any_ty = self.getAnyStructType(); - // Any = { type_tag: i64, value: i64 } - const tag = c.LLVMConstInt(self.cached_i64, self.anyTag(ba.source_type), 0); - // Bitcast value to i64, using SExt for signed types, ZExt otherwise - const is_signed = self.isSignedTypeEx(ba.source_type); - const val_as_i64 = if (is_signed) self.coerceToI64Signed(val) else self.coerceToI64(val); - var result = c.LLVMGetUndef(any_ty); - result = c.LLVMBuildInsertValue(self.builder, result, tag, 0, "ba.tag"); - result = c.LLVMBuildInsertValue(self.builder, result, val_as_i64, 1, "ba.val"); - self.mapRef(result); - }, - .unbox_any => |un| { - const any_val = self.resolveRef(un.operand); - const any_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(any_val)); - if (any_kind == c.LLVMStructTypeKind) { - const raw = c.LLVMBuildExtractValue(self.builder, any_val, 1, "ua.raw"); - const target_ty = self.toLLVMType(instruction.ty); - self.mapRef(self.coerceFromI64(raw, target_ty)); - } else { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } - }, + .box_any => |ba| self.ops().emitBoxAny(ba), + .unbox_any => |un| self.ops().emitUnboxAny(instruction, un), // ── Reflection ops ────────────────────────────────────── - .field_name_get => |fr| { - // Build global string array for this struct's field names, then GEP at runtime index - const global = self.reflection().getOrBuildFieldNameArray(fr.struct_type); - const idx = self.resolveRef(fr.index); - const string_ty = self.getStringStructType(); - // Get struct field count for array type - const field_info = self.ir_mod.types.get(fr.struct_type); - const field_count: u32 = switch (field_info) { - .@"struct" => |s| @intCast(s.fields.len), - .@"union" => |u| @intCast(u.fields.len), - .tagged_union => |u| @intCast(u.fields.len), - .@"enum" => |e| @intCast(e.variants.len), - else => 0, - }; - const array_ty = c.LLVMArrayType(string_ty, field_count); - const zero = c.LLVMConstInt(self.cached_i64, 0, 0); - var indices = [2]c.LLVMValueRef{ zero, idx }; - const gep = c.LLVMBuildInBoundsGEP2(self.builder, array_ty, global, &indices, 2, "fn.gep"); - const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "fn.load"); - self.mapRef(result); - }, - .field_value_get => |fr| { - // Switch on index, each case: extractvalue field k → box as Any - self.emitFieldValueGet(fr, func_idx); - }, - .error_tag_name_get => |u| { - // Tag id → name: GEP into the always-linked tag-name table at - // the runtime tag id (the error-set value, a u32). Out-of-range - // ids can't occur — ids come from the same registry the table - // is built from — so no bounds branch is needed. - const global = self.reflection().getOrBuildTagNameArray(); - const tag_raw = self.resolveRef(u.operand); - const idx = c.LLVMBuildZExt(self.builder, tag_raw, self.cached_i64, "etn.idx"); - const string_ty = self.getStringStructType(); - const n: u32 = @intCast(self.ir_mod.types.tags.names.items.len); - const array_ty = c.LLVMArrayType(string_ty, n); - const zero = c.LLVMConstInt(self.cached_i64, 0, 0); - var indices = [2]c.LLVMValueRef{ zero, idx }; - const gep = c.LLVMBuildInBoundsGEP2(self.builder, array_ty, global, &indices, 2, "etn.gep"); - const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "etn.load"); - self.mapRef(result); - }, + .field_name_get => |fr| self.ops().emitFieldNameGet(fr), + .field_value_get => |fr| self.ops().emitFieldValueGet(fr, func_idx), + .error_tag_name_get => |u| self.ops().emitErrorTagNameGet(u), // ── Switch branch ──────────────────────────────────────── - .switch_br => |sw| { - const operand = self.resolveRef(sw.operand); - const default_bb = self.getBlock(func_idx, sw.default); - const switch_inst = c.LLVMBuildSwitch(self.builder, operand, default_bb, @intCast(sw.cases.len)); - for (sw.cases) |case| { - const case_val = c.LLVMConstInt(c.LLVMTypeOf(operand), @bitCast(case.value), 0); - const case_bb = self.getBlock(func_idx, case.target); - c.LLVMAddCase(switch_inst, case_val, case_bb); - } - self.advanceRefCounter(); - }, + .switch_br => |sw| self.ops().emitSwitchBr(sw, func_idx), // ── Closure creation ───────────────────────────────────── - .closure_create => |cc| { - const fn_val = self.func_map.get(cc.func.index()) orelse c.LLVMGetUndef(self.cached_ptr); - const env_val = if (cc.env.isNone()) c.LLVMConstNull(self.cached_ptr) else self.resolveRef(cc.env); - const closure_ty = self.getClosureStructType(); - var result = c.LLVMGetUndef(closure_ty); - result = c.LLVMBuildInsertValue(self.builder, result, fn_val, 0, "cc.fn"); - result = c.LLVMBuildInsertValue(self.builder, result, env_val, 1, "cc.env"); - self.mapRef(result); - }, + .closure_create => |cc| self.ops().emitClosureCreate(cc), // ── Vector ops ─────────────────────────────────────────── - .vec_splat => |un| { - const scalar = self.resolveRef(un.operand); - const vec_ty = self.toLLVMType(instruction.ty); - const vec_len = c.LLVMGetVectorSize(vec_ty); - // Build a splat: insertelement into undef for each lane - var result = c.LLVMGetUndef(vec_ty); - var i: c_uint = 0; - while (i < vec_len) : (i += 1) { - const idx_val = c.LLVMConstInt(self.cached_i32, i, 0); - result = c.LLVMBuildInsertElement(self.builder, result, scalar, idx_val, "splat"); - } - self.mapRef(result); - }, - .vec_extract => |bin| { - const vec = self.resolveRef(bin.lhs); - const idx = self.resolveRef(bin.rhs); - self.mapRef(c.LLVMBuildExtractElement(self.builder, vec, idx, "vext")); - }, - .vec_insert => |tri| { - const vec = self.resolveRef(tri.a); - const idx = self.resolveRef(tri.b); - const val = self.resolveRef(tri.c); - self.mapRef(c.LLVMBuildInsertElement(self.builder, vec, val, idx, "vins")); - }, + .vec_splat => |un| self.ops().emitVecSplat(instruction, un), + .vec_extract => |bin| self.ops().emitVecExtract(bin), + .vec_insert => |tri| self.ops().emitVecInsert(tri), // ── Block params ───────────────────────────────────────── - .block_param => |bp| { - // Create a PHI node — incoming values are filled in by fixupPhiNodes - const ty = self.toLLVMType(instruction.ty); - const phi = c.LLVMBuildPhi(self.builder, ty, "bp"); - self.pending_phis.append(self.alloc, .{ - .phi = phi, - .block_id = bp.block, - .param_index = bp.param_index, - }) catch unreachable; - self.mapRef(phi); - }, + .block_param => |bp| self.ops().emitBlockParam(instruction, bp), // ── Misc ───────────────────────────────────────────────── - .placeholder => { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - }, + .placeholder => self.ops().emitPlaceholder(instruction), } } @@ -1706,7 +1506,7 @@ pub const LLVMEmitter = struct { return self.ref_map.get(ref.index()) orelse c.LLVMGetUndef(self.cached_i64); } - fn getBlock(self: *LLVMEmitter, func_idx: u32, block_id: BlockId) c.LLVMBasicBlockRef { + pub fn getBlock(self: *LLVMEmitter, func_idx: u32, block_id: BlockId) c.LLVMBasicBlockRef { const key = makeBlockKey(func_idx, block_id.index()); return self.block_map.get(key) orelse { std.debug.print("getBlock: missing block func={d} block={d}\n", .{ func_idx, block_id.index() }); @@ -2180,7 +1980,7 @@ pub const LLVMEmitter = struct { // ── Value coercion helpers ────────────────────────────────────── /// Coerce any scalar value to i64 for boxing into Any. - fn coerceToI64(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { + pub fn coerceToI64(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { const ty = c.LLVMTypeOf(val); const kind = c.LLVMGetTypeKind(ty); if (kind == c.LLVMVoidTypeKind) { @@ -2214,7 +2014,7 @@ pub const LLVMEmitter = struct { } /// Coerce signed integer to i64 using sign-extension. - fn coerceToI64Signed(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { + pub fn coerceToI64Signed(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { const ty = c.LLVMTypeOf(val); const kind = c.LLVMGetTypeKind(ty); if (kind == c.LLVMIntegerTypeKind) { @@ -2229,7 +2029,7 @@ pub const LLVMEmitter = struct { } /// Check if a TypeId represents a signed integer type (including arbitrary-width). - fn isSignedTypeEx(self: *LLVMEmitter, ty: TypeId) bool { + pub fn isSignedTypeEx(self: *LLVMEmitter, ty: TypeId) bool { if (isSignedType(ty)) return true; if (!ty.isBuiltin()) { const info = self.ir_mod.types.get(ty); @@ -2248,7 +2048,7 @@ pub const LLVMEmitter = struct { /// which also uses TypeId indices for type-switch comparisons. /// For arbitrary-width ints (user-defined signed/unsigned), map to the closest /// builtin TypeId so the "case int:" branch matches correctly. - fn anyTag(self: *LLVMEmitter, ty: TypeId) u64 { + pub fn anyTag(self: *LLVMEmitter, ty: TypeId) u64 { if (ty.isBuiltin()) return ty.index(); // For user-defined types, check if they're arbitrary-width ints const info = self.ir_mod.types.get(ty); @@ -2272,7 +2072,7 @@ pub const LLVMEmitter = struct { } /// Coerce i64 back to the target type for unboxing from Any. - fn coerceFromI64(self: *LLVMEmitter, val: c.LLVMValueRef, target: c.LLVMTypeRef) c.LLVMValueRef { + pub fn coerceFromI64(self: *LLVMEmitter, val: c.LLVMValueRef, target: c.LLVMTypeRef) c.LLVMValueRef { const kind = c.LLVMGetTypeKind(target); if (kind == c.LLVMPointerTypeKind) { return c.LLVMBuildIntToPtr(self.builder, val, target, "i2p"); @@ -2679,7 +2479,7 @@ pub const LLVMEmitter = struct { /// name from the always-linked tag-name table, hand it + the tag to /// `sx_trace_report_unhandled` (prints the header + return trace to stderr), /// and `ret i32 1`. - fn emitFailableMainRet(self: *LLVMEmitter, value: ?c.LLVMValueRef, tag_val: c.LLVMValueRef) void { + pub fn emitFailableMainRet(self: *LLVMEmitter, value: ?c.LLVMValueRef, tag_val: c.LLVMValueRef) void { const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder)); const tag_i32 = self.coerceArg(tag_val, self.cached_i32); @@ -2719,7 +2519,7 @@ pub const LLVMEmitter = struct { } /// Emit field_value_get: switch on runtime index, each case extracts a field and boxes it as Any. - fn emitFieldValueGet(self: *LLVMEmitter, fr: ir_inst.FieldReflect, func_idx: u32) void { + pub fn emitFieldValueGet(self: *LLVMEmitter, fr: ir_inst.FieldReflect, func_idx: u32) void { const base_val = self.resolveRef(fr.base); const idx_val = self.resolveRef(fr.index); From f52a24a0fbcceb5053903c31633175c2c59b8869 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 12:56:28 +0300 Subject: [PATCH 15/26] refactor(sema): seal sema.zig as editor indexing only (A8.1) Remove the last compiler dependency on sema as semantic truth and stop publishing as-you-type sema diagnostics from the LSP. - core.zig: drop dead `Compilation.analyze()`, the `sema_result` field, and the sema->diagnostics merge; drop the now-orphaned sema import. The CLI pipeline (parse -> resolveImports -> generateCode) never called analyze(), so this removes only dead code. - lsp/server.zig: rename `analyzeAndPublish` -> `refreshEditorIndex` and delete its sema-diagnostic publish (and the now-unused `semaToLspDiags`). The editor index (doc.sema) is still refreshed for nav/refs/completion/ tokens. On-save/on-open diagnostics still come solely from the canonical compiler pipeline in `runProjectCheck` (unchanged). - Document sema as an editor-indexing API (doc.sema field comment). Intended behavior change: as-you-type sema diagnostics no longer publish; on-save canonical diagnostics are the sole source. CLI compile output and the 361-example suite are unchanged (361/0, zero snapshot churn). --- src/core.zig | 14 -------------- src/lsp/document.zig | 4 +++- src/lsp/server.zig | 43 +++++++++---------------------------------- 3 files changed, 12 insertions(+), 49 deletions(-) diff --git a/src/core.zig b/src/core.zig index 5c88d98..fdc9cd1 100644 --- a/src/core.zig +++ b/src/core.zig @@ -2,7 +2,6 @@ const std = @import("std"); const ast = @import("ast.zig"); const parser = @import("parser.zig"); const imports = @import("imports.zig"); -const sema = @import("sema.zig"); const errors = @import("errors.zig"); const c_import = @import("c_import.zig"); const ir = @import("ir/ir.zig"); @@ -27,7 +26,6 @@ pub const Compilation = struct { import_sources: std.StringHashMap([:0]const u8), module_scopes: std.StringHashMap(std.StringHashMap(void)), import_graph: std.StringHashMap(std.StringHashMap(void)), - sema_result: ?sema.SemaResult = null, ir_emitter: ?ir.LLVMEmitter = null, /// Lowered IR module, kept alive past `generateCode` so post-link /// callbacks can re-enter the interpreter to invoke sx functions @@ -128,18 +126,6 @@ pub const Compilation = struct { self.resolved_root = new_root; } - pub fn analyze(self: *Compilation) !void { - const root = self.resolved_root orelse self.root orelse return error.CompileError; - var analyzer = sema.Analyzer.init(self.allocator); - self.sema_result = analyzer.analyze(root) catch return error.CompileError; - // Merge sema diagnostics into our list - if (self.sema_result) |sr| { - for (sr.diagnostics) |d| { - self.diagnostics.add(d.level, d.message, d.span); - } - } - } - /// Generate code via the IR pipeline: lower AST → IR → LLVM. pub fn generateCode(self: *Compilation) !void { // Heap-allocate the IR module so its address is stable during emit diff --git a/src/lsp/document.zig b/src/lsp/document.zig index f013c12..6f5e461 100644 --- a/src/lsp/document.zig +++ b/src/lsp/document.zig @@ -23,7 +23,9 @@ pub const Document = struct { version: i64, /// AST root for this file only (not merged). root: ?*sx.ast.Node, - /// Sema results for this file (references are relative to this source). + /// Editor index for this file — symbols/references/types for navigation, + /// completion, and hover (references are relative to this source). Not a + /// diagnostic source; see `sema.zig` module doc. sema: ?sx.sema.SemaResult, /// Last successful sema (preserved across parse failures for completions). last_good_sema: ?sx.sema.SemaResult = null, diff --git a/src/lsp/server.zig b/src/lsp/server.zig index 47ee5dd..201c46e 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -158,7 +158,7 @@ pub const Server = struct { const text = jsonStr(jsonGet(td, "text") orelse return) orelse return; const version = jsonInt(jsonGet(td, "version") orelse return) orelse return; - try self.analyzeAndPublish(uri, text, version); + try self.refreshEditorIndex(uri, text, version); self.runProjectCheck(); } @@ -173,7 +173,7 @@ pub const Server = struct { const last = changes_arr[changes_arr.len - 1]; const text = jsonStr(jsonGet(last, "text") orelse return) orelse return; - try self.analyzeAndPublish(uri, text, version); + try self.refreshEditorIndex(uri, text, version); } fn handleDidClose(_: *Server, params: std.json.Value) void { @@ -1919,41 +1919,16 @@ pub const Server = struct { // ---- Core analysis pipeline ---- - fn analyzeAndPublish(self: *Server, uri: []const u8, text: []const u8, version: i64) !void { + /// Refresh the editor index for a document (symbols/references/types that + /// power navigation, completion, hover, and token classification). Publishes + /// no diagnostics — authoritative diagnostics come only from the canonical + /// compiler pipeline in `runProjectCheck`. + fn refreshEditorIndex(self: *Server, uri: []const u8, text: []const u8, version: i64) !void { const file_path = uriToFilePath(uri) orelse ""; const source = try self.allocator.dupeZ(u8, text); const doc = try self.documents.openOrUpdate(file_path, source, version); self.documents.analyzeDocument(doc) catch {}; - - // Publish diagnostics from sema - if (doc.sema) |sema| { - try self.sendDiagnostics(uri, semaToLspDiags(self.allocator, doc.source, sema.diagnostics)); - } else { - try self.sendDiagnostics(uri, &.{}); - } - } - - fn semaToLspDiags(allocator: std.mem.Allocator, source: [:0]const u8, diags: []const sx.errors.Diagnostic) []const lsp.Diagnostic { - var result = std.ArrayList(lsp.Diagnostic).empty; - for (diags) |d| { - const range = if (d.span) |span| spanToRange(source, span) else lsp.Range{ - .start = .{ .line = 0, .character = 0 }, - .end = .{ .line = 0, .character = 1 }, - }; - const severity: u32 = switch (d.level) { - .err => 1, - .warn => 2, - .note => 3, - .help => 4, - }; - result.append(allocator, .{ - .range = range, - .severity = severity, - .message = d.message, - }) catch continue; - } - return result.items; } fn sendDiagnostics(self: *Server, uri: []const u8, diagnostics: []const lsp.Diagnostic) !void { @@ -2012,8 +1987,8 @@ pub const Server = struct { } /// Drive the whole-program check from the workspace entry point and publish - /// the real compiler's diagnostics per file (runs on save; the sema layer - /// keeps live per-keystroke feedback). + /// the real compiler's diagnostics per file. Runs on open and save; this is + /// the sole source of LSP diagnostics (the editor index publishes none). fn runProjectCheck(self: *Server) void { if (self.root_path.len == 0) return; const entry_path = std.fmt.allocPrint(self.allocator, "{s}/main.sx", .{self.root_path}) catch return; From e13dbfeb94b3f0e8d7779e1e1a6504db84cf76ff Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 13:21:00 +0300 Subject: [PATCH 16/26] refactor(types): shrink src/types.zig to editor/parse metadata (A8.2) Remove the legacy parallel type model's compiler-like surface. The compiler pipeline resolves/lowers/lays out against canonical src/ir/types.zig (TypeId/TypeTable); src/types.zig.Type is now strictly editor-indexing + parse-time name metadata. - src/types.zig: delete the type-resolution surface (widen, bitWidth, isImplicitlyConvertibleTo) and every helper left dead once it was gone (eql, isInt/isFloat/isSigned/isUnsigned, isTuple/isVector, and the already-unused classification predicates isEnum/isUnion/isString/ isStringLike/isAny/optionalChild/sliceElementType/manyPointerElementType/ vectorElementType/isFunctionType/isClosureType/isCallable). Keep the Type union plus the display/name-classification helpers sema/lsp/parser use (fromName, fromTypeExpr, toName, displayName, isStruct/isOptional/isSlice/ isPointer/isManyPointer/isArray, pointerPointeeType). Seal the file with a doc comment. - src/sema.zig: inferExprType no longer calls Type.widen for arithmetic; it approximates the display type as the left operand's (no second resolver in the editor index). - src/ir/type_bridge.zig: delete the dead bridgeType (legacy Type -> TypeId) function + its sole sx_types import; resolveAstType and the AST->TypeId path are untouched. - src/ir/ir.zig: drop the bridgeType re-export. - src/ir/type_bridge.test.zig: drop the two bridgeType tests (function gone). Gate: zig build, zig build test (exit 0), tests/run_examples.sh 361/0, zero examples/expected churn. --- src/ir/ir.zig | 1 - src/ir/type_bridge.test.zig | 36 ---- src/ir/type_bridge.zig | 115 ------------ src/sema.zig | 10 +- src/types.zig | 363 +----------------------------------- 5 files changed, 14 insertions(+), 511 deletions(-) diff --git a/src/ir/ir.zig b/src/ir/ir.zig index 144f77d..ceb0000 100644 --- a/src/ir/ir.zig +++ b/src/ir/ir.zig @@ -64,7 +64,6 @@ pub const LLVMEmitter = emit_llvm.LLVMEmitter; pub const type_bridge = @import("type_bridge.zig"); pub const resolveAstType = type_bridge.resolveAstType; -pub const bridgeType = type_bridge.bridgeType; pub const jni_descriptor = @import("jni_descriptor.zig"); pub const jni_java_emit = @import("jni_java_emit.zig"); diff --git a/src/ir/type_bridge.test.zig b/src/ir/type_bridge.test.zig index 4b0312e..571eea4 100644 --- a/src/ir/type_bridge.test.zig +++ b/src/ir/type_bridge.test.zig @@ -9,42 +9,6 @@ const TypeId = types.TypeId; const TypeInfo = types.TypeInfo; const TypeTable = types.TypeTable; -test "bridgeType: primitives" { - const alloc = std.testing.allocator; - var table = TypeTable.init(alloc); - defer table.deinit(); - - try std.testing.expectEqual(TypeId.s32, type_bridge.bridgeType(.{ .signed = 32 }, &table, null)); - try std.testing.expectEqual(TypeId.u8, type_bridge.bridgeType(.{ .unsigned = 8 }, &table, null)); - try std.testing.expectEqual(TypeId.f64, type_bridge.bridgeType(.f64, &table, null)); - try std.testing.expectEqual(TypeId.void, type_bridge.bridgeType(.void_type, &table, null)); - try std.testing.expectEqual(TypeId.bool, type_bridge.bridgeType(.boolean, &table, null)); - try std.testing.expectEqual(TypeId.string, type_bridge.bridgeType(.string_type, &table, null)); - try std.testing.expectEqual(TypeId.any, type_bridge.bridgeType(.any_type, &table, null)); -} - -test "bridgeType: composite types" { - const alloc = std.testing.allocator; - var table = TypeTable.init(alloc); - defer table.deinit(); - - // Pointer - const ptr_id = type_bridge.bridgeType(.{ .pointer_type = .{ .pointee_name = "s32" } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .pointer = .{ .pointee = .s32 } }, table.get(ptr_id)); - - // Slice - const slice_id = type_bridge.bridgeType(.{ .slice_type = .{ .element_name = "u8" } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .slice = .{ .element = .u8 } }, table.get(slice_id)); - - // Array - const arr_id = type_bridge.bridgeType(.{ .array_type = .{ .element_name = "f32", .length = 4 } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .array = .{ .element = .f32, .length = 4 } }, table.get(arr_id)); - - // Optional - const opt_id = type_bridge.bridgeType(.{ .optional_type = .{ .child_name = "s64" } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .optional = .{ .child = .s64 } }, table.get(opt_id)); -} - test "resolveAstType: primitive type_expr" { const alloc = std.testing.allocator; var table = TypeTable.init(alloc); diff --git a/src/ir/type_bridge.zig b/src/ir/type_bridge.zig index c5662c5..6145395 100644 --- a/src/ir/type_bridge.zig +++ b/src/ir/type_bridge.zig @@ -2,7 +2,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ast = @import("../ast.zig"); const Node = ast.Node; -const sx_types = @import("../types.zig"); const ir_types = @import("types.zig"); const TypeId = ir_types.TypeId; const TypeInfo = ir_types.TypeInfo; @@ -106,122 +105,8 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap }; } -// ── types.Type → TypeId ───────────────────────────────────────────────── -// Translate an existing codegen Type value into an IR TypeId. Used when -// we have access to the codegen's resolved type info (Phase 3+). - -pub fn bridgeType(ty: sx_types.Type, table: *TypeTable, alias_map: AliasMap) TypeId { - return switch (ty) { - .signed => |w| switch (w) { - 8 => .s8, - 16 => .s16, - 32 => .s32, - 64 => .s64, - // Non-standard width: intern the exact width rather than quantising - // to s64 (which would silently change the type's size). - else => table.intern(.{ .signed = w }), - }, - .unsigned => |w| switch (w) { - 8 => .u8, - 16 => .u16, - 32 => .u32, - 64 => .u64, - else => table.intern(.{ .unsigned = w }), - }, - .f32 => .f32, - .f64 => .f64, - .void_type => .void, - .boolean => .bool, - .string_type => .string, - .any_type => .any, - .usize_type => .usize, - .isize_type => .isize, - .enum_type => |name| resolveNamedType(name, .@"enum", table), - .struct_type => |name| resolveNamedType(name, .@"struct", table), - .union_type => |name| resolveNamedType(name, .@"union", table), - .array_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.arrayOf(elem, info.length); - }, - .slice_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.sliceOf(elem); - }, - .pointer_type => |info| blk: { - const pointee = resolveTypeName(info.pointee_name, table, alias_map); - break :blk table.ptrTo(pointee); - }, - .many_pointer_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.manyPtrTo(elem); - }, - .optional_type => |info| blk: { - const child = resolveTypeName(info.child_name, table, alias_map); - break :blk table.optionalOf(child); - }, - .vector_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.vectorOf(elem, info.length); - }, - .function_type => |info| blk: { - const alloc = table.alloc; - var param_ids = std.ArrayList(TypeId).empty; - for (info.param_types) |pt| { - param_ids.append(alloc, bridgeType(pt, table, alias_map)) catch unreachable; - } - const ret_id = bridgeType(info.return_type.*, table, alias_map); - break :blk table.functionType(param_ids.items, ret_id); - }, - .closure_type => |info| blk: { - const alloc = table.alloc; - var param_ids = std.ArrayList(TypeId).empty; - for (info.param_types) |pt| { - param_ids.append(alloc, bridgeType(pt, table, alias_map)) catch unreachable; - } - const ret_id = bridgeType(info.return_type.*, table, alias_map); - break :blk table.closureType(param_ids.items, ret_id); - }, - .tuple_type => |info| blk: { - const alloc = table.alloc; - var field_ids = std.ArrayList(TypeId).empty; - for (info.field_types) |ft| { - field_ids.append(alloc, bridgeType(ft, table, alias_map)) catch unreachable; - } - var name_ids: ?[]const StringId = null; - if (info.field_names) |names| { - var ids = std.ArrayList(StringId).empty; - for (names) |n| { - ids.append(alloc, table.internString(n)) catch unreachable; - } - name_ids = ids.items; - } - break :blk table.intern(.{ .tuple = .{ - .fields = field_ids.items, - .names = name_ids, - } }); - }, - .meta_type => .any, // meta types map to Any for now - .unresolved => .unresolved, - }; -} - // ── Internal helpers ───────────────────────────────────────────────────── -const NamedKind = enum { @"struct", @"enum", @"union" }; - -fn resolveNamedType(name: []const u8, kind: NamedKind, table: *TypeTable) TypeId { - // Check if primitive first - if (resolveTypePrimitive(name)) |id| return id; - - // Register as a named type - const name_id = table.internString(name); - return switch (kind) { - .@"struct" => table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }), - .@"enum" => table.intern(.{ .@"enum" = .{ .name = name_id, .variants = &.{} } }), - .@"union" => table.intern(.{ .@"union" = .{ .name = name_id, .fields = &.{} } }), - }; -} - /// Resolve a bare type name. The algorithm lives in `type_resolver.zig` /// (`TypeResolver.resolveNamed`, the single source); `type_bridge` forwards the /// caller-threaded `alias_map` (the single-source `ProgramIndex.type_alias_map`). diff --git a/src/sema.zig b/src/sema.zig index 01391e2..ef24a7a 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -565,7 +565,7 @@ pub const Analyzer = struct { /// Infer an approximate editor `Type` for an expression (hover/completion; /// metadata only — NOT a compiler type decision, which uses `TypeId`). /// Uses fn_signatures for call return types, struct_types for field access, - /// symbols for identifier types, and Type.widen for arithmetic promotion. + /// and symbols for identifier types. pub fn inferExprType(self: *Analyzer, node: *const Node) Type { return switch (node.data) { .int_literal => Type.s(64), @@ -578,9 +578,13 @@ pub const Analyzer = struct { switch (binop.op) { .eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op, .in_op => return .boolean, else => { + // Editor display only: approximate an arithmetic result as + // its left operand's type (or the right when the left is + // unresolved). Numeric promotion is a compiler decision on + // `TypeId`, never recomputed here. const lhs_ty = self.inferExprType(binop.lhs); - const rhs_ty = self.inferExprType(binop.rhs); - return Type.widen(lhs_ty, rhs_ty); + if (lhs_ty == .unresolved) return self.inferExprType(binop.rhs); + return lhs_ty; }, } }, diff --git a/src/types.zig b/src/types.zig index 2a98a91..3c2d1f7 100644 --- a/src/types.zig +++ b/src/types.zig @@ -2,12 +2,13 @@ const std = @import("std"); const ast = @import("ast.zig"); const Node = ast.Node; -/// Editor metadata type model, used only by `src/sema.zig` (the language-server -/// symbol/type index) for navigation, completion, and hover. NOT the compiler's -/// source of truth: lowering, codegen, and layout use the canonical -/// `TypeId` / `TypeTable` model in `src/ir/types.zig`. Do not expand this to -/// carry new compiler semantics; the architecture endpoint (phase A8) is to -/// delete it or reduce it to display-only data derived from `TypeId`. +/// Editor-indexing and parse-time name metadata — used by `src/sema.zig` (the +/// language-server symbol/type index) for navigation, completion, and hover, and +/// by `src/parser.zig` for parse-time primitive-name classification. This is NOT +/// a compiler type model: it carries no type-resolution surface (no widening, +/// convertibility, or layout). The canonical model the compiler resolves, lowers, +/// and lays out against is `TypeId` / `TypeTable` in `src/ir/types.zig`. Keep this +/// display- and classification-only; never add resolution semantics here. pub const Type = union(enum) { // Variable-width integers (1–64 bits) signed: u8, @@ -86,61 +87,6 @@ pub const Type = union(enum) { field_types: []const Type, }; - /// Content-based equality: compares string fields by content, not pointer identity. - pub fn eql(self: Type, other: Type) bool { - const Tag = std.meta.Tag(Type); - const self_tag: Tag = self; - const other_tag: Tag = other; - if (self_tag != other_tag) return false; - return switch (self) { - .signed => |w| w == other.signed, - .unsigned => |w| w == other.unsigned, - .f32, .f64, .void_type, .boolean, .string_type, .any_type, .usize_type, .isize_type, .unresolved => true, - .enum_type => |n| std.mem.eql(u8, n, other.enum_type), - .struct_type => |n| std.mem.eql(u8, n, other.struct_type), - .union_type => |n| std.mem.eql(u8, n, other.union_type), - .array_type => |info| info.length == other.array_type.length and - std.mem.eql(u8, info.element_name, other.array_type.element_name), - .slice_type => |info| std.mem.eql(u8, info.element_name, other.slice_type.element_name), - .pointer_type => |info| std.mem.eql(u8, info.pointee_name, other.pointer_type.pointee_name), - .many_pointer_type => |info| std.mem.eql(u8, info.element_name, other.many_pointer_type.element_name), - .vector_type => |info| info.length == other.vector_type.length and - std.mem.eql(u8, info.element_name, other.vector_type.element_name), - .function_type => |info| { - const o = other.function_type; - if (info.param_types.len != o.param_types.len) return false; - for (info.param_types, o.param_types) |a, b| { - if (!a.eql(b)) return false; - } - return info.return_type.eql(o.return_type.*); - }, - .closure_type => |info| { - const o = other.closure_type; - if (info.param_types.len != o.param_types.len) return false; - for (info.param_types, o.param_types) |a, b| { - if (!a.eql(b)) return false; - } - return info.return_type.eql(o.return_type.*); - }, - .optional_type => |info| std.mem.eql(u8, info.child_name, other.optional_type.child_name), - .meta_type => |info| std.mem.eql(u8, info.name, other.meta_type.name), - .tuple_type => |info| { - const o = other.tuple_type; - if (info.field_types.len != o.field_types.len) return false; - for (info.field_types, o.field_types) |a, b| { - if (!a.eql(b)) return false; - } - // If both have names, compare them - if (info.field_names != null and o.field_names != null) { - for (info.field_names.?, o.field_names.?) |a, b| { - if (!std.mem.eql(u8, a, b)) return false; - } - } - return true; - }, - }; - } - // Convenience constructors pub fn s(width: u8) Type { return .{ .signed = width }; @@ -255,13 +201,6 @@ pub const Type = union(enum) { return fromName(node.data.type_expr.name); } - pub fn isEnum(self: Type) bool { - return switch (self) { - .enum_type => true, - else => false, - }; - } - pub fn isStruct(self: Type) bool { return switch (self) { .struct_type => true, @@ -269,20 +208,6 @@ pub const Type = union(enum) { }; } - pub fn isUnion(self: Type) bool { - return switch (self) { - .union_type => true, - else => false, - }; - } - - pub fn isTuple(self: Type) bool { - return switch (self) { - .tuple_type => true, - else => false, - }; - } - pub fn isOptional(self: Type) bool { return switch (self) { .optional_type => true, @@ -290,33 +215,6 @@ pub const Type = union(enum) { }; } - pub fn optionalChild(self: Type) ?[]const u8 { - return switch (self) { - .optional_type => |info| info.child_name, - else => null, - }; - } - - pub fn isAny(self: Type) bool { - return switch (self) { - .any_type => true, - else => false, - }; - } - - pub fn isString(self: Type) bool { - return self == .string_type; - } - - /// Returns true for both `string` (null-terminated) and `[]u8` (byte slice) - pub fn isStringLike(self: Type) bool { - if (self == .string_type) return true; - if (self.isSlice()) { - return std.mem.eql(u8, self.slice_type.element_name, "u8"); - } - return false; - } - pub fn isSlice(self: Type) bool { return switch (self) { .slice_type => true, @@ -324,13 +222,6 @@ pub const Type = union(enum) { }; } - pub fn sliceElementType(self: Type) ?Type { - return switch (self) { - .slice_type => |info| fromName(info.element_name), - else => null, - }; - } - pub fn isPointer(self: Type) bool { return switch (self) { .pointer_type => true, @@ -352,35 +243,6 @@ pub const Type = union(enum) { }; } - pub fn manyPointerElementType(self: Type) ?Type { - return switch (self) { - .many_pointer_type => |info| fromName(info.element_name), - else => null, - }; - } - - pub fn isFunctionType(self: Type) bool { - return switch (self) { - .function_type => true, - else => false, - }; - } - - pub fn isClosureType(self: Type) bool { - return switch (self) { - .closure_type => true, - else => false, - }; - } - - /// Returns true for both bare function pointers and closures - pub fn isCallable(self: Type) bool { - return switch (self) { - .function_type, .closure_type => true, - else => false, - }; - } - pub fn isArray(self: Type) bool { return switch (self) { .array_type => true, @@ -388,156 +250,6 @@ pub const Type = union(enum) { }; } - pub fn isVector(self: Type) bool { - return switch (self) { - .vector_type => true, - else => false, - }; - } - - pub fn vectorElementType(self: Type) ?Type { - return switch (self) { - .vector_type => |info| fromName(info.element_name), - else => null, - }; - } - - pub fn isFloat(self: Type) bool { - return switch (self) { - .f32, .f64 => true, - else => false, - }; - } - - pub fn isInt(self: Type) bool { - return self.isSigned() or self.isUnsigned(); - } - - pub fn isSigned(self: Type) bool { - return switch (self) { - .signed => true, - else => false, - }; - } - - pub fn isUnsigned(self: Type) bool { - return switch (self) { - .unsigned => true, - else => false, - }; - } - - pub fn bitWidth(self: Type) u32 { - return switch (self) { - .signed => |w| w, - .unsigned => |w| w, - .f32 => 32, - .f64 => 64, - .boolean => 1, - .pointer_type, .many_pointer_type, .function_type => 64, - .closure_type => 128, // { ptr, ptr } = 16 bytes - else => 0, - }; - } - - /// Check if this type can be implicitly converted to `target` without `xx`. - /// Safe (implicit) conversions: - /// - Same type - /// - Both unsigned int, target width >= source width - /// - Both signed int, target width >= source width - /// - Unsigned to signed, target width strictly > source width - /// - Any int to any float - /// - Float to wider float (f32 → f64) - /// Everything else requires `xx`. - pub fn isImplicitlyConvertibleTo(self: Type, target: Type) bool { - if (self.eql(target)) return true; - // string <-> []u8: same layout, bidirectional implicit conversion - if (self == .string_type and target.isSlice() and - std.mem.eql(u8, target.slice_type.element_name, "u8")) return true; - if (self.isSlice() and std.mem.eql(u8, self.slice_type.element_name, "u8") and - target == .string_type) return true; - // *void is universal pointer (both directions) - if (self.isPointer() and target.isPointer()) { - if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true; - if (std.mem.eql(u8, target.pointer_type.pointee_name, "void")) return true; - } - // *T → [*]T: pointer to element is implicitly convertible to many-pointer - // null (*void) → [*]T is also allowed - if (self.isPointer() and target.isManyPointer()) { - if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true; - return std.mem.eql(u8, self.pointer_type.pointee_name, target.many_pointer_type.element_name); - } - // [*]T → *void: any many-pointer converts to void pointer - if (self.isManyPointer() and target.isPointer()) { - return std.mem.eql(u8, target.pointer_type.pointee_name, "void"); - } - - // Tuple → tuple: same field count and each field implicitly convertible - if (self.isTuple() and target.isTuple()) { - const si = self.tuple_type; - const ti = target.tuple_type; - if (si.field_types.len != ti.field_types.len) return false; - for (si.field_types, ti.field_types) |sf, tf| { - if (!sf.isImplicitlyConvertibleTo(tf)) return false; - } - return true; - } - - // T → ?T: any type implicitly wraps into its optional - if (target.isOptional()) { - const child_name = target.optional_type.child_name; - // null → ?T - if (self.isPointer() and std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true; - // ?T → ?U when T → U - if (self.isOptional()) { - const self_child = fromName(self.optional_type.child_name) orelse return false; - const target_child = fromName(child_name) orelse return false; - return self_child.isImplicitlyConvertibleTo(target_child); - } - // T → ?T: check if self matches the child type - if (fromName(child_name)) |child_type| { - return self.eql(child_type) or self.isImplicitlyConvertibleTo(child_type); - } - // Non-primitive child (struct/enum name): compare by name - return switch (self) { - .struct_type => |n| std.mem.eql(u8, n, child_name), - .enum_type => |n| std.mem.eql(u8, n, child_name), - .union_type => |n| std.mem.eql(u8, n, child_name), - else => false, - }; - } - - const src_float = self.isFloat(); - const dst_float = target.isFloat(); - const src_int = self.isInt(); - - // Float → wider float - if (src_float and dst_float) { - return target.bitWidth() >= self.bitWidth(); - } - - // Int → float (always safe) - if (src_int and dst_float) return true; - - // Both unsigned → target width >= source width - if (self.isUnsigned() and target.isUnsigned()) { - return target.bitWidth() >= self.bitWidth(); - } - - // Both signed → target width >= source width - if (self.isSigned() and target.isSigned()) { - return target.bitWidth() >= self.bitWidth(); - } - - // Unsigned → signed: target must be strictly wider - if (self.isUnsigned() and target.isSigned()) { - return target.bitWidth() > self.bitWidth(); - } - - // Everything else requires xx - return false; - } - fn fmtAlloc(allocator: std.mem.Allocator, comptime fmt: []const u8, args: anytype) ![]const u8 { var buf: [128]u8 = undefined; const result = std.fmt.bufPrint(&buf, fmt, args) catch @@ -621,65 +333,4 @@ pub const Type = union(enum) { }, }; } - - /// Widen two types to a common type for binary operations. - /// Used for arithmetic type promotion (e.g., s16 + s32 → s32, int + float → float). - pub fn widen(a: Type, b: Type) Type { - // Same type → return it - if (a.eql(b)) return a; - - // Tuple + tuple → return a if same field count - if (a.isTuple() and b.isTuple()) { - if (a.tuple_type.field_types.len == b.tuple_type.field_types.len) return a; - } - - // Vector + vector of same dimensions → return a - if (a.isVector() and b.isVector()) return a; - // Vector + scalar → return vector (scalar will be broadcast) - if (a.isVector() and !b.isVector()) return a; - if (b.isVector() and !a.isVector()) return b; - - const a_float = a.isFloat(); - const b_float = b.isFloat(); - const a_int = a.isInt(); - const b_int = b.isInt(); - - // Both float → wider float - if (a_float and b_float) { - return if (a.bitWidth() >= b.bitWidth()) a else b; - } - - // int + float → float - if (a_int and b_float) return b; - if (b_int and a_float) return a; - - // Both signed → wider signed - if (a.isSigned() and b.isSigned()) { - return Type.s(@intCast(@max(a.bitWidth(), b.bitWidth()))); - } - - // Both unsigned → wider unsigned - if (a.isUnsigned() and b.isUnsigned()) { - return Type.u(@intCast(@max(a.bitWidth(), b.bitWidth()))); - } - - // signed + unsigned (mixed) - if (a_int and b_int) { - const aw = a.bitWidth(); - const bw = b.bitWidth(); - const max_w = @max(aw, bw); - // If same width, need one extra bit for sign; otherwise max is enough - const need: u32 = if (aw == bw) max_w + 1 else max_w; - const capped: u8 = @intCast(@min(need, 128)); - return Type.s(capped); - } - - // Optional types: widen inner types - if (a.isOptional() and b.isOptional()) return a; - - // Pointer types: both are pointers → return first (all are opaque ptr at LLVM level) - if ((a.isPointer() or a.isManyPointer()) and (b.isPointer() or b.isManyPointer())) return a; - - return a; - } }; From badf2af2986749442d9016165ca5a3634b265527 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 13:52:38 +0300 Subject: [PATCH 17/26] docs(debugger): point DWARF/Frame wiring at backend/llvm helpers (A9.2) Refresh the debugging architecture reference for the A7.2 relocation: DWARF emission lives in src/backend/llvm/debug.zig (DebugInfo) and the interned Frame / tag-name tables in src/backend/llvm/reflection.zig (Reflection); emit_llvm.zig is the orchestrator that owns LLVMEmitter and dispatches to them. Behavior is unchanged; only the file-and-function map, the 'what's emitted' home, and the debugEnabled() owner are corrected. --- docs/debugger.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/debugger.md b/docs/debugger.md index 6c66467..fd3b56b 100644 --- a/docs/debugger.md +++ b/docs/debugger.md @@ -105,10 +105,12 @@ niladic, span-stamped IR op** — the same pattern as `is_comptime` / `interp_print_frames`. It carries **no operands and no global reference**; each backend derives the frame from its own context: -- **`emit_llvm`:** resolves the op's `span` + current function → - `{file, line, col, func}` (reusing the source map wired in for DWARF), - **interns and builds the `Frame` global in `emit_llvm`** (the same - mechanism as the tag-name table), then emits `call sx_trace_push(ptr)`. +- **`emit_llvm` (the `.trace_frame` arm):** resolves the op's `span` + + current function → `{file, line, col, func}` (reusing the source map + wired in for DWARF), **interns and builds the `Frame` global** in + [`src/backend/llvm/reflection.zig`](../src/backend/llvm/reflection.zig) + (the same mechanism, in the same file, as the tag-name table), then emits + `call sx_trace_push(ptr)`. - **`interp`:** pushes the packed `(func_id, ir_offset)` from its own execution context. @@ -118,8 +120,9 @@ alternative — an op carrying a `GlobalId` to an IR-level `Frame` global — would make the global visible to the interpreter (forcing comptime onto the pointer-deref path) and fatten the lowerer; **do not do this.** -`Frame` is defined **once** in sx (`trace.sx`/std); `emit_llvm` builds the -interned global off that `TypeId` through the normal struct-emission path, +`Frame` is defined **once** in sx (`trace.sx`/std); the reflection builder +(`src/backend/llvm/reflection.zig`) builds the interned global off that +`TypeId` through the normal struct-emission path, never a bespoke byte layout (which would risk the "8-bytes-assumed" clobber class of bug). `file`/`func` strings are interned into a shared pool so a path shared by N push sites is stored once — the table stays @@ -193,8 +196,9 @@ stripped without affecting traces. ### What's emitted -In [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig), gated on the same -debug opt levels + a wired source map (`setDebugContext`): +In [`src/backend/llvm/debug.zig`](../src/backend/llvm/debug.zig) (the +`DebugInfo` helper, driven from `emit_llvm`'s `emit()` pipeline), gated on +the same debug opt levels + a wired source map (`setDebugContext`): - one `DICompileUnit` + `DIFile` on the main file, - a `DISubprogram` per emitted function (`LLVMSetSubprogram`), @@ -237,7 +241,9 @@ both the trace path and the DWARF path. Items marked ✅ exist today; |---|---| | [`src/core.zig`](../src/core.zig) | `Compilation`: owns `import_sources` (file→source map), constructs the emitter, calls `setDebugContext` + `emit`; re-enters the interpreter for `#run`/post-link | | [`src/ir/lower.zig`](../src/ir/lower.zig) | AST→IR. Stamps `Inst.span`; emits push/clear at failure/absorb sites; `tracesEnabled` gate; declares the `sx_trace_*` externs | -| [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig) | IR→LLVM. Builds the interned `Frame` table; lowers the push op to a pointer push; emits all DWARF metadata | +| [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig) | IR→LLVM orchestrator. Owns `LLVMEmitter` + the source map (`setDebugContext`); dispatches the push op and the DWARF passes to the helpers below | +| [`src/backend/llvm/reflection.zig`](../src/backend/llvm/reflection.zig) | `Reflection`: builds the interned `Frame` table + the tag-name / type-name tables; lowers the push op to a pointer push | +| [`src/backend/llvm/debug.zig`](../src/backend/llvm/debug.zig) | `DebugInfo`: builds all DWARF metadata (compile unit, per-function subprograms, per-instruction `DILocation`) | | [`src/ir/interp.zig`](../src/ir/interp.zig) | Comptime IR interpreter. Lowers the push op to a packed `(func_id, offset)`; resolves comptime frames | | [`src/errors.zig`](../src/errors.zig) | `SourceLoc.compute(source, offset) → {line, col}`; the `import_sources` map type | | [`src/ir/inst.zig`](../src/ir/inst.zig) | `Inst.span`, `Function.source_file`, the `Op` union (home of the trace-push op) | @@ -330,8 +336,8 @@ the failable-`main` wrapper, whose `ret` path in `emit_llvm` ### The gate: one switch, two consumers -`Lowering.tracesEnabled()` (lower.zig) and `LLVMEmitter.debugEnabled()` -(emit_llvm) both reduce to `opt_level == .none or .less`. The `Frame` +`Lowering.tracesEnabled()` (lower.zig) and `DebugInfo.debugEnabled()` +(backend/llvm/debug.zig) both reduce to `opt_level == .none or .less`. The `Frame` table + push/clear ride `tracesEnabled`; DWARF rides `debugEnabled`. Release (`-O2`/`-O3`) emits neither. `sx run` defaults to `-O0` (both on); `sx ir`/`sx asm` default to `-O2` (both off) — which is why the `.ir` From 5cb16912654bded44d45ffbd8333e91c445df05b Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 14:03:44 +0300 Subject: [PATCH 18/26] docs(debugger): correct trace-frame op name and sx_trace_push attribution (A9.2) Name the niladic op `.trace_frame` (no `.trace_frame_push` op exists) in the trace-path roadmap, matching the rest of the doc and src/ir/inst.zig. Describe the `.trace_frame` arm as building/interning the Frame global and yielding its address as the op's value; the separate sx_trace_push call is emitted by the lowerer via normal call lowering, not by the arm itself. --- docs/debugger.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/debugger.md b/docs/debugger.md index fd3b56b..08e5540 100644 --- a/docs/debugger.md +++ b/docs/debugger.md @@ -109,8 +109,9 @@ each backend derives the frame from its own context: current function → `{file, line, col, func}` (reusing the source map wired in for DWARF), **interns and builds the `Frame` global** in [`src/backend/llvm/reflection.zig`](../src/backend/llvm/reflection.zig) - (the same mechanism, in the same file, as the tag-name table), then emits - `call sx_trace_push(ptr)`. + (the same mechanism, in the same file, as the tag-name table), and yields + its address as the op's value. The lowerer feeds that value to a separate + `sx_trace_push` call emitted through the normal call lowering. - **`interp`:** pushes the packed `(func_id, ir_offset)` from its own execution context. @@ -276,13 +277,15 @@ traces and DWARF can never disagree: 1. `lower.zig` reaches a failure site — `lowerRaise`, `lowerTry`'s propagation branch, `lowerFailableOr`, or `lowerDestructureDecl` — and - (when `tracesEnabled()`) emits the niladic `.trace_frame_push` op, - replacing today's `emitTracePush(placeholderTraceFrame())`. Absorbing - sites emit `emitTraceClear()` → `call sx_trace_clear()`. -2. **Compiled backend** (`emit_llvm.emitInst`, `.trace_frame_push` arm): + (when `tracesEnabled()`) emits the niladic `.trace_frame` op via + `placeholderTraceFrame()`, whose result feeds a separate `sx_trace_push` + call via `emitTracePush()`. Absorbing sites emit `emitTraceClear()` → + `call sx_trace_clear()`. +2. **Compiled backend** (`emit_llvm.emitInst`, `.trace_frame` arm): resolve the op's `span` + current function → `{file,line,col,func}`, intern into the `Frame` table (built alongside `tag_name_array`), and - emit `call sx_trace_push(ptr_to_Frame)`. The `sx_trace_push` extern is + yield the `Frame` global's address as the op's value, which the separate + `sx_trace_push` call (step 1) consumes. The `sx_trace_push` extern is declared lazily by `getTraceFids()` (which sets `needs_trace_runtime`). 3. **Interpreter** (`interp.zig`, same op): pack `(current_func_id, ir_offset)` into a `u64` and call the foreign `sx_trace_push` (resolved From e6c51359fe9c158c9d4c3a5aa76916a7feeef4f4 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 14:17:24 +0300 Subject: [PATCH 19/26] docs(debugger): align trace-push mechanism to one ground-truth model (A9.2) The .trace_frame op is niladic: it carries no operand and no GlobalId. The compiled backend yields the interned Frame global's address as the op's value (reflection.emitTraceFrame); the interpreter yields a packed (func_id, ir_offset) as the op's value and never calls sx_trace_push (recovered later by .trace_resolve). The sx_trace_push call is a separate call op emitted by lower.zig at each push site, consuming the op's value. Reword every passage that stated the old/wrong model: the niladic invariant is about the op (not the push site emitting only one instruction); reflection yields the op's value rather than lowering a push; the interp returns the packed value rather than calling the foreign sx_trace_push via host_ffi dlsym. --- docs/debugger.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/debugger.md b/docs/debugger.md index 08e5540..c1487d9 100644 --- a/docs/debugger.md +++ b/docs/debugger.md @@ -100,10 +100,11 @@ never collide. ### The niladic trace-push op -Because the same IR runs in both machines, the push is a **dedicated, -niladic, span-stamped IR op** — the same pattern as `is_comptime` / -`interp_print_frames`. It carries **no operands and no global reference**; -each backend derives the frame from its own context: +Because the same IR runs in both machines, the frame value comes from a +**dedicated, niladic, span-stamped IR op** (`.trace_frame`) — the same +pattern as `is_comptime` / `interp_print_frames`. It carries **no operands +and no global reference**; each backend derives the frame from its own +context: - **`emit_llvm` (the `.trace_frame` arm):** resolves the op's `span` + current function → `{file, line, col, func}` (reusing the source map @@ -112,14 +113,16 @@ each backend derives the frame from its own context: (the same mechanism, in the same file, as the tag-name table), and yields its address as the op's value. The lowerer feeds that value to a separate `sx_trace_push` call emitted through the normal call lowering. -- **`interp`:** pushes the packed `(func_id, ir_offset)` from its own - execution context. +- **`interp`:** yields the packed `(func_id, ir_offset)` from its own + execution context as the op's value; no `sx_trace_push` call runs — the + packed value is recovered later by the comptime `.trace_resolve` resolver. -This keeps the lowerer thin: at each push site it emits the op and nothing -else — no operand wiring, no global construction. The rejected -alternative — an op carrying a `GlobalId` to an IR-level `Frame` global — -would make the global visible to the interpreter (forcing comptime onto -the pointer-deref path) and fatten the lowerer; **do not do this.** +The op stays niladic by design: it carries no operand and no `GlobalId`, +so no IR-level `Frame` global is ever visible to the interpreter. The +rejected alternative — an op carrying a `GlobalId` to an IR-level `Frame` +global — would make the global visible to the interpreter (forcing +comptime onto the pointer-deref path) and fatten the lowerer; **do not do +this.** `Frame` is defined **once** in sx (`trace.sx`/std); the reflection builder (`src/backend/llvm/reflection.zig`) builds the interned global off that @@ -242,12 +245,12 @@ both the trace path and the DWARF path. Items marked ✅ exist today; |---|---| | [`src/core.zig`](../src/core.zig) | `Compilation`: owns `import_sources` (file→source map), constructs the emitter, calls `setDebugContext` + `emit`; re-enters the interpreter for `#run`/post-link | | [`src/ir/lower.zig`](../src/ir/lower.zig) | AST→IR. Stamps `Inst.span`; emits push/clear at failure/absorb sites; `tracesEnabled` gate; declares the `sx_trace_*` externs | -| [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig) | IR→LLVM orchestrator. Owns `LLVMEmitter` + the source map (`setDebugContext`); dispatches the push op and the DWARF passes to the helpers below | -| [`src/backend/llvm/reflection.zig`](../src/backend/llvm/reflection.zig) | `Reflection`: builds the interned `Frame` table + the tag-name / type-name tables; lowers the push op to a pointer push | +| [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig) | IR→LLVM orchestrator. Owns `LLVMEmitter` + the source map (`setDebugContext`); dispatches the `.trace_frame` op and the DWARF passes to the helpers below | +| [`src/backend/llvm/reflection.zig`](../src/backend/llvm/reflection.zig) | `Reflection`: builds the interned `Frame` table + the tag-name / type-name tables; yields the `.trace_frame` op's value (the `Frame` global's address) — the `sx_trace_push` call itself is emitted by `lower.zig` | | [`src/backend/llvm/debug.zig`](../src/backend/llvm/debug.zig) | `DebugInfo`: builds all DWARF metadata (compile unit, per-function subprograms, per-instruction `DILocation`) | -| [`src/ir/interp.zig`](../src/ir/interp.zig) | Comptime IR interpreter. Lowers the push op to a packed `(func_id, offset)`; resolves comptime frames | +| [`src/ir/interp.zig`](../src/ir/interp.zig) | Comptime IR interpreter. Lowers the `.trace_frame` op to a packed `(func_id, offset)`; resolves comptime frames | | [`src/errors.zig`](../src/errors.zig) | `SourceLoc.compute(source, offset) → {line, col}`; the `import_sources` map type | -| [`src/ir/inst.zig`](../src/ir/inst.zig) | `Inst.span`, `Function.source_file`, the `Op` union (home of the trace-push op) | +| [`src/ir/inst.zig`](../src/ir/inst.zig) | `Inst.span`, `Function.source_file`, the `Op` union (home of the `.trace_frame` op) | | [`library/vendors/sx_trace_runtime/sx_trace.c`](../library/vendors/sx_trace_runtime/sx_trace.c) | the thread-local ring buffer + `sx_trace_report_unhandled` | | [`library/modules/trace.sx`](../library/modules/trace.sx) | the formatter (`to_string` / `print_current`) | | [`src/llvm_api.zig`](../src/llvm_api.zig) | binds `llvm-c/Core.h` + `llvm-c/DebugInfo.h` | @@ -288,8 +291,10 @@ traces and DWARF can never disagree: `sx_trace_push` call (step 1) consumes. The `sx_trace_push` extern is declared lazily by `getTraceFids()` (which sets `needs_trace_runtime`). 3. **Interpreter** (`interp.zig`, same op): pack `(current_func_id, - ir_offset)` into a `u64` and call the foreign `sx_trace_push` (resolved - via `host_ffi` `dlsym` against the linked `sx_trace.c`). + ir_offset)` into a `u64` and return it as the op's value. The interp + never calls `sx_trace_push`; the packed value is recovered later by the + comptime `.trace_resolve` resolver, which turns it back into + `file:line:col` via the IR/source tables. **Buffer (run time) ✅** — `sx_trace.c` stores the `u64`s. Linked into the compiler so the JIT resolves `sx_trace_*` via `dlsym`; auto-injected as a @@ -297,7 +302,7 @@ compiler so the JIT resolves `sx_trace_*` via `dlsym`; auto-injected as a **Formatter (run time) ✅ (compiled 3a, comptime 3b)** — `trace.sx` `to_string()` loops `sx_trace_len()` / `sx_trace_frame_at(i)` and resolves each `u64` through -a **read-side context-split primitive** (the mirror of the push op): +a **read-side context-split primitive** (the mirror of the `.trace_frame` op): - compiled: cast the `u64` → `*Frame`, load the fields. - comptime: unpack `(func_id, offset)`, resolve via the interpreter's From e907fc9e01ce8fe1e557a3889956e959ef3796f0 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 14:28:28 +0300 Subject: [PATCH 20/26] docs(debugger): describe Frame global build as LLVMConstNamedStruct over getFrameStructType (A9.2) The compiled backend builds each trace Frame global as an LLVM named-struct constant over the cached getFrameStructType() layout (file, line, col, func, line_text) via LLVMConstNamedStruct -- a type-safe LLVM struct, not the sx Frame TypeId / normal struct-emission path. Also correct the file field to the source basename (full paths live in DWARF). --- docs/debugger.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/debugger.md b/docs/debugger.md index c1487d9..0d2968d 100644 --- a/docs/debugger.md +++ b/docs/debugger.md @@ -124,14 +124,19 @@ global — would make the global visible to the interpreter (forcing comptime onto the pointer-deref path) and fatten the lowerer; **do not do this.** -`Frame` is defined **once** in sx (`trace.sx`/std); the reflection builder -(`src/backend/llvm/reflection.zig`) builds the interned global off that -`TypeId` through the normal struct-emission path, -never a bespoke byte layout (which would risk the "8-bytes-assumed" -clobber class of bug). `file`/`func` strings are interned into a shared -pool so a path shared by N push sites is stored once — the table stays -tiny. File paths are normalized to a stable relative form so trace output -is machine-independent and snapshot-testable. +`Frame` is defined **once** in sx (`trace.sx`/std), and its runtime layout — +`{ string file, i32 line, i32 col, string func, string line_text }` — is +mirrored by the cached LLVM named-struct type `getFrameStructType()` +(`src/ir/emit_llvm.zig`). The reflection builder +(`src/backend/llvm/reflection.zig`) assembles each push site's global as an +LLVM **named-struct constant** over that cached type via +`LLVMConstNamedStruct` — a type-safe LLVM struct, not hand-packed bytes +(which would risk the "8-bytes-assumed" clobber class of bug). It does +**not** derive the layout from the sx `Frame` `TypeId`, nor route through +the normal struct-emission path. `file`/`func`/`line_text` strings are +interned into a shared pool so a path shared by N push sites is stored once +— the table stays tiny. The `file` field is the source basename (full paths +live in DWARF), so trace output is machine-independent and snapshot-testable. ### Push and clear sites From 0e5b79ddabf92b3ec4564ae13b707d6dee5d6acb Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 14:36:08 +0300 Subject: [PATCH 21/26] docs(debugger): call getFrameStructType a literal (anonymous) struct type (A9.2) --- docs/debugger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/debugger.md b/docs/debugger.md index 0d2968d..8a4e624 100644 --- a/docs/debugger.md +++ b/docs/debugger.md @@ -126,7 +126,7 @@ this.** `Frame` is defined **once** in sx (`trace.sx`/std), and its runtime layout — `{ string file, i32 line, i32 col, string func, string line_text }` — is -mirrored by the cached LLVM named-struct type `getFrameStructType()` +mirrored by the cached LLVM **literal (anonymous) struct type** `getFrameStructType()` (`src/ir/emit_llvm.zig`). The reflection builder (`src/backend/llvm/reflection.zig`) assembles each push site's global as an LLVM **named-struct constant** over that cached type via From e5d9d1fec187ecfe3e73ed1f39a9527fc9969c15 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 14:49:23 +0300 Subject: [PATCH 22/26] docs(debugger): correct interp push-call model and span.start term (A9.2) The interp's .trace_frame op only yields the packed value; the separate sx_trace_push call op is executed by the interp as a foreign call via host_ffi/dlsym, so the prior 'no sx_trace_push call runs' / 'never calls sx_trace_push' phrasing was wrong. The packed low word is the op's span.start (a source byte offset), not an IR instruction offset; renamed every ir_offset/offset reference to span.start. --- docs/debugger.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/debugger.md b/docs/debugger.md index 8a4e624..85aa934 100644 --- a/docs/debugger.md +++ b/docs/debugger.md @@ -40,7 +40,7 @@ to satisfy all three. "JIT" and "comptime" are **not** the same thing. |---|---|---| | **AOT** (`sx build`) | native machine code in an on-disk binary | pointer to an interned `Frame` | | **JIT** (`sx run`) | ORC-JIT'd machine code in anonymous memory | pointer to an interned `Frame` | -| **Comptime** (`#run`) | the IR interpreter (`interp.zig`) — no machine code | packed `(func_id, ir_offset)` | +| **Comptime** (`#run`) | the IR interpreter (`interp.zig`) — no machine code | packed `(func_id, span.start)` | The crucial constraint: **the same lowered IR runs in the compiled backend *and* the interpreter.** So a value the IR produces (like a trace @@ -92,11 +92,12 @@ so the location — *and the offending source line itself* (`line_text`, for the formatter reads it directly. No PC capture, no DWARF, no symbolizer, no runtime file read. -A comptime frame is instead a packed `(func_id: u32, ir_offset: u32)`, -resolved through the interpreter's in-memory IR/source tables. The -interpreter **never dereferences the compiled `Frame` pointer** — it uses -its own representation — so the compiled and interpreted memory models -never collide. +A comptime frame is instead a packed `(func_id: u32, span.start: u32)` — +where `span.start` is the op's source byte offset — resolved through the +interpreter's in-memory IR/source tables. The interpreter **never +dereferences the compiled `Frame` pointer** — it uses its own +representation — so the compiled and interpreted memory models never +collide. ### The niladic trace-push op @@ -113,9 +114,12 @@ context: (the same mechanism, in the same file, as the tag-name table), and yields its address as the op's value. The lowerer feeds that value to a separate `sx_trace_push` call emitted through the normal call lowering. -- **`interp`:** yields the packed `(func_id, ir_offset)` from its own - execution context as the op's value; no `sx_trace_push` call runs — the - packed value is recovered later by the comptime `.trace_resolve` resolver. +- **`interp`:** yields the packed `(func_id, span.start)` from its own + execution context as the op's value. The separate `sx_trace_push` call + op consuming it is executed by the interp as a foreign call (via + `host_ffi`/dlsym, the same path as any extern), storing the packed value + in the buffer; the comptime `.trace_resolve` resolver later recovers + `file:line:col` from it. The op stays niladic by design: it carries no operand and no `GlobalId`, so no IR-level `Frame` global is ever visible to the interpreter. The @@ -253,7 +257,7 @@ both the trace path and the DWARF path. Items marked ✅ exist today; | [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig) | IR→LLVM orchestrator. Owns `LLVMEmitter` + the source map (`setDebugContext`); dispatches the `.trace_frame` op and the DWARF passes to the helpers below | | [`src/backend/llvm/reflection.zig`](../src/backend/llvm/reflection.zig) | `Reflection`: builds the interned `Frame` table + the tag-name / type-name tables; yields the `.trace_frame` op's value (the `Frame` global's address) — the `sx_trace_push` call itself is emitted by `lower.zig` | | [`src/backend/llvm/debug.zig`](../src/backend/llvm/debug.zig) | `DebugInfo`: builds all DWARF metadata (compile unit, per-function subprograms, per-instruction `DILocation`) | -| [`src/ir/interp.zig`](../src/ir/interp.zig) | Comptime IR interpreter. Lowers the `.trace_frame` op to a packed `(func_id, offset)`; resolves comptime frames | +| [`src/ir/interp.zig`](../src/ir/interp.zig) | Comptime IR interpreter. The `.trace_frame` op yields a packed `(func_id, span.start)`; the separate `sx_trace_push` call op runs as a foreign call (dlsym); `.trace_resolve` recovers comptime frames | | [`src/errors.zig`](../src/errors.zig) | `SourceLoc.compute(source, offset) → {line, col}`; the `import_sources` map type | | [`src/ir/inst.zig`](../src/ir/inst.zig) | `Inst.span`, `Function.source_file`, the `Op` union (home of the `.trace_frame` op) | | [`library/vendors/sx_trace_runtime/sx_trace.c`](../library/vendors/sx_trace_runtime/sx_trace.c) | the thread-local ring buffer + `sx_trace_report_unhandled` | @@ -296,9 +300,11 @@ traces and DWARF can never disagree: `sx_trace_push` call (step 1) consumes. The `sx_trace_push` extern is declared lazily by `getTraceFids()` (which sets `needs_trace_runtime`). 3. **Interpreter** (`interp.zig`, same op): pack `(current_func_id, - ir_offset)` into a `u64` and return it as the op's value. The interp - never calls `sx_trace_push`; the packed value is recovered later by the - comptime `.trace_resolve` resolver, which turns it back into + span.start)` into a `u64` and return it as the op's value. The separate + `sx_trace_push` call op is then executed by the interp as a foreign call + (`callForeign` → `host_ffi.lookupSymbol`/dlsym, the same path as any + extern), storing the packed value in the buffer. The comptime + `.trace_resolve` resolver later turns each packed value back into `file:line:col` via the IR/source tables. **Buffer (run time) ✅** — `sx_trace.c` stores the `u64`s. Linked into the @@ -310,7 +316,7 @@ compiler so the JIT resolves `sx_trace_*` via `dlsym`; auto-injected as a a **read-side context-split primitive** (the mirror of the `.trace_frame` op): - compiled: cast the `u64` → `*Frame`, load the fields. -- comptime: unpack `(func_id, offset)`, resolve via the interpreter's +- comptime: unpack `(func_id, span.start)`, resolve via the interpreter's IR/source tables → a `Frame`. The same `trace.sx` source works in both because it runs in the matching @@ -474,7 +480,7 @@ a Mach-O debug map, never register JIT DWARF. | IR instructions carry source spans | ✅ done — E3.0 slice 1 (`b44a5d0`) | | DWARF emission (compile unit / subprogram / line table) | ✅ done — E3.0 slice 2 (`c32d694`) | | Niladic trace-push op + interned `Frame` table (runtime) | ✅ done — E3.3 slice 3a (`1b6cbc1`) | -| Comptime resolver (`func_id, ir_offset` → location) | ✅ done — slice 3b | +| Comptime resolver (`func_id, span.start` → location) | ✅ done — slice 3b | | Source snippet + `^` caret | ✅ done — slice 3c (line embedded in `Frame`) | | `--emit-obj` artifact plumbing | ✅ done — slice 3d | | Stepping verification: macOS lldb | ✅ done — 3e rung 1 (`tests/debug_stepping_smoke.sh`) | From a7ddbeb85b23f4386f6055fa742fa9ea411f3ffe Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 15:02:09 +0300 Subject: [PATCH 23/26] docs(error-handling): trace locations come from embedded Frame metadata, not DWARF (A9.2) --- docs/error-handling.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/error-handling.md b/docs/error-handling.md index 251eb82..c746801 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -326,7 +326,9 @@ error trace: Traces are on by default in debug builds and compiled out in release (re-enable with `--release-traces`). They cost nothing on the success -path. Frame locations resolve through the binary's debug info, so +path. Each frame's location comes from `Frame` metadata +(file/line/col/func) baked in at the trace point — the trace resolves +itself with no debug info. Separately, sx emits standard DWARF, so `lldb` / `gdb` work on sx binaries too. Interpolating a tag with `{}` prints its **name**, not a number — in From 4537538bb2aac6b13a7c196839872ae6e0058ed2 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 15:43:27 +0300 Subject: [PATCH 24/26] fix(ffi): replace silent `.void` arg-type fallback with loud `.unresolved` (issue 0074) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four FFI call-arg lowering sites resolved an argument's IR type via `getRefIRType(arg_ref) orelse .void` — a silent fallback to the load-bearing real type `.void`. A failed lookup there is a codegen invariant violation, but `.void` is treated by downstream `toLLVMType` → `abiCoerceParamType` → `coerceArg` as a legitimate void-typed foreign argument, corrupting the call ABI with no diagnostic. Add one shared resolver `LLVMEmitter.argIRTypeOrFail` that returns the dedicated `.unresolved` sentinel on a failed lookup — never `.void`/`.s64` — so the failure cannot masquerade as a real type and trips `toLLVMType`'s existing hard `@panic` tripwire at the call site. Route all four sites through it: - src/ir/emit_llvm.zig JNI constructor (NewObject) arg loop - src/backend/llvm/ops.zig objc_msgSend arg loop - src/backend/llvm/ops.zig JNI non-virtual call arg loop - src/backend/llvm/ops.zig JNI CallMethod arg loop Happy path is byte-identical (every real arg already has a resolved type); FFI examples stay green with zero snapshot churn. Regression test (fail-before/pass-after) in src/ir/emit_llvm.test.zig asserts an unresolvable FFI arg ref now yields `.unresolved`, not the old silent `.void`. --- issues/0074-ffi-arg-type-void-fallback.md | 84 +++++++++++++++++++++++ src/backend/llvm/ops.zig | 6 +- src/ir/emit_llvm.test.zig | 47 +++++++++++++ src/ir/emit_llvm.zig | 12 +++- 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 issues/0074-ffi-arg-type-void-fallback.md diff --git a/issues/0074-ffi-arg-type-void-fallback.md b/issues/0074-ffi-arg-type-void-fallback.md new file mode 100644 index 0000000..e10efd9 --- /dev/null +++ b/issues/0074-ffi-arg-type-void-fallback.md @@ -0,0 +1,84 @@ +# 0074 — silent `getRefIRType(arg_ref) orelse .void` fallback in FFI call-arg lowering + +> **✅ RESOLVED.** Root cause: four FFI call-arg lowering loops resolved an +> argument's IR type via `getRefIRType(arg_ref) orelse .void` — a silent fallback +> to the load-bearing real type `.void`, which downstream `toLLVMType` → +> `abiCoerceParamType` → `coerceArg` treat as a legitimate (void-typed) foreign +> argument, corrupting the call ABI with no diagnostic. Fix: one shared resolver +> `LLVMEmitter.argIRTypeOrFail` ([src/ir/emit_llvm.zig]) returns the dedicated +> `.unresolved` sentinel on a failed lookup — never `.void`/`.s64` — so the failure +> cannot masquerade as a real type and trips `toLLVMType`'s existing hard `@panic` +> tripwire at the call site. All four sites +> ([src/ir/emit_llvm.zig] JNI constructor; [src/backend/llvm/ops.zig] objc_msgSend, +> JNI non-virtual, JNI `CallMethod`) now route through the helper. Happy path +> is byte-identical (every real arg already has a resolved type) — FFI examples stay +> green with zero snapshot churn. Regression test (fail-before/pass-after): +> `src/ir/emit_llvm.test.zig` — "argIRTypeOrFail surfaces .unresolved for an +> unresolvable FFI arg ref (issue 0074)". + +## Symptom +**One-line:** Four FFI call-arg lowering sites silently default a failed +argument-type lookup to `.void` — the forbidden silent-type-fallback anti-pattern +(`.void` as a failed-type-lookup sentinel), which would produce a void-typed +foreign-call argument (wrong LLVM param type → silent ABI corruption) with no +diagnostic if the lookup ever fails. + +**Observed:** `self.getRefIRType(arg_ref) orelse .void` at: +- `src/ir/emit_llvm.zig:2463` +- `src/backend/llvm/ops.zig:517` (Obj-C `objc_msgSend` arg loop) +- `src/backend/llvm/ops.zig:731` (JNI non-virtual call arg loop) +- `src/backend/llvm/ops.zig:761` (JNI `CallMethod` arg loop) + +Each then does `toLLVMType(raw_ty)` → `abiCoerceParamType` → `coerceArg`, so a +`.void` fallback silently mis-types the foreign-call argument. + +**Expected:** `getRefIRType` returning null for a real call argument is a +"must-succeed lookup" failure (every arg is a valid param/instruction ref). Per +`CLAUDE.md` REJECTED PATTERNS — *"`.void` is an UNACCEPTABLE sentinel for a failed +type lookup"* — the lookup failure must surface as a diagnostic / hard tripwire, not +a silently-corrupted argument type. + +## Provenance / scope +Pre-existing pattern (the `emit_llvm.zig` site is original; the three `ops.zig` sites +were relocated **verbatim, behavior-preserving** by step A7.4c of the arch-refactor — +the flow reviewer/observer correctly approved the relocations as equivalence-preserving). +Surfaced by the **A9.3 final fallback-audit** of the arch-refactor stream. Not +introduced by the refactor; filed per the IMPASSIBLE RULE (*"If you find an existing +default-return in the compiler that swallows a lookup failure, treat it as a +discovered bug — file an issue, do not just delete the default in place"*). + +## Reproduction +This is a **latent / static** finding: there is no known sx program that drives +`getRefIRType` to `null` for a valid foreign-call argument (well-formed IR always +has a type for every arg ref), so it cannot currently be triggered at runtime — which +is exactly why it is dangerous (a future IR change that breaks the invariant would +corrupt FFI ABI silently). The code paths are exercised (and must stay green after +the fix) by the existing FFI examples, e.g.: + +``` +examples/13xx-ffi-objc-* # objc_msgSend arg lowering (ops.zig:517) +examples/14xx-ffi-jni-* # JNI CallMethod / non-virtual (ops.zig:731/761) +``` + +(No new minimal repro `.sx` is meaningful for a latent defensive fallback; the fix is +verified by (a) the FFI suite staying green and (b) a unit test that asserts the new +loud-failure path, see below.) + +## Investigation prompt (ready to paste into a fresh session) +> In `/Users/agra/projects/sx`, four FFI call-arg lowering sites use the forbidden +> silent type-fallback `self.getRefIRType(arg_ref) orelse .void` +> (`src/ir/emit_llvm.zig:2463`; `src/backend/llvm/ops.zig:517`, `:731`, `:761`). +> `getRefIRType` (`src/ir/emit_llvm.zig:2229`, returns `?TypeId`) yields `null` only +> when a ref is neither a function param nor a block instruction result — a +> must-not-happen case for a real call argument. Replace the silent `.void` default +> with a loud failure that cannot be mistaken for a real type, per `CLAUDE.md` +> REJECTED PATTERNS: emit a diagnostic via `self.diagnostics.addFmt(.err, span, +> "...", .{...})` and/or a hard tripwire (`@panic`/`bailDetail`-style) naming the op +> and the bad ref — do NOT substitute another real type. Prefer a single shared +> helper (e.g. `argIRTypeOrFail(arg_ref, span)`) used by all four sites so the policy +> lives in one place. Then: (1) `/Users/agra/.zvm/bin/zig build && /Users/agra/.zvm/bin/zig +> build test && bash tests/run_examples.sh` must stay 361/0 with the FFI examples +> green (the happy path is unchanged); (2) add a `*.test.zig` unit test that +> constructs an FFI call op with a bogus arg ref and asserts the loud failure fires +> (not a `.void` silent default). Expected new behavior: an unresolved FFI arg type +> produces a clear compiler error / panic, never a void-typed foreign argument. diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index e34bbb1..e2f529e 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -514,7 +514,7 @@ pub const Ops = struct { // coercion applied so structs / strings decay the // same way they do for any C foreign call. for (msg.args, 0..) |arg_ref, i| { - const raw_ty = self.e.getRefIRType(arg_ref) orelse .void; + const raw_ty = self.e.argIRTypeOrFail(arg_ref); const raw_llvm = self.e.toLLVMType(raw_ty); const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm); param_types[i + 2 + sret_off] = coerced_ty; @@ -728,7 +728,7 @@ pub const Ops = struct { call_args_nv[2] = cls; call_args_nv[3] = mid_val; for (msg.args, 0..) |arg_ref, i| { - const raw_ty = self.e.getRefIRType(arg_ref) orelse .void; + const raw_ty = self.e.argIRTypeOrFail(arg_ref); const raw_llvm = self.e.toLLVMType(raw_ty); const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm); call_param_types_nv[i + 4] = coerced_ty; @@ -758,7 +758,7 @@ pub const Ops = struct { call_args[1] = target; call_args[2] = mid; for (msg.args, 0..) |arg_ref, i| { - const raw_ty = self.e.getRefIRType(arg_ref) orelse .void; + const raw_ty = self.e.argIRTypeOrFail(arg_ref); const raw_llvm = self.e.toLLVMType(raw_ty); const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm); call_param_types[i + 3] = coerced_ty; diff --git a/src/ir/emit_llvm.test.zig b/src/ir/emit_llvm.test.zig index 8d9426f..ff20cda 100644 --- a/src/ir/emit_llvm.test.zig +++ b/src/ir/emit_llvm.test.zig @@ -1097,3 +1097,50 @@ test "emit: ERR E3.0 — no DWARF without a debug context (unit-test default)" { try std.testing.expect(std.mem.indexOf(u8, ir_str, "DICompileUnit") == null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "!dbg") == null); } + +// ── issue 0074: FFI arg-type lookup must fail loudly, never silently `.void` ── +// `argIRTypeOrFail` backs the four FFI call-arg lowering sites (objc_msgSend, +// JNI CallMethod / non-virtual / constructor). A ref it cannot resolve is +// a codegen invariant violation; it must surface the dedicated `.unresolved` +// tripwire sentinel (which `toLLVMType` hard-panics on) rather than the old +// silent `.void` default that would emit a void-typed foreign-call argument. +test "emit: argIRTypeOrFail surfaces .unresolved for an unresolvable FFI arg ref (issue 0074)" { + const alloc = std.testing.allocator; + var module = Module.init(alloc); + defer module.deinit(); + + var b = Builder.init(&module); + + // func ffifn(a: s64, b: f64) -> void { } + const fid = b.beginFunction(str(&module, "ffifn"), &[_]Function.Param{ + .{ .name = str(&module, "a"), .ty = .s64 }, + .{ .name = str(&module, "b"), .ty = .f64 }, + }, .void); + const entry = b.appendBlock(str(&module, "entry"), &.{}); + b.switchToBlock(entry); + b.retVoid(); + b.finalize(); + + var emitter = LLVMEmitter.init(alloc, &module, "test_ffi_argty", .{}); + defer emitter.deinit(); + emitter.current_func_idx = fid.index(); + + // Happy path: a real arg ref (param 0 / param 1) resolves byte-identically + // to its declared IR type — the FFI fast path is unchanged. + try std.testing.expectEqual(TypeId.s64, emitter.argIRTypeOrFail(Ref.fromIndex(0))); + try std.testing.expectEqual(TypeId.f64, emitter.argIRTypeOrFail(Ref.fromIndex(1))); + + // A ref past every param and instruction is unresolvable. + const bogus = Ref.fromIndex(100_000); + try std.testing.expectEqual(@as(?TypeId, null), emitter.getRefIRType(bogus)); + + // Fail-before: the old `getRefIRType(arg) orelse .void` would silently + // yield `.void` here — a real, load-bearing type that downstream ABI + // coercion treats as a legitimate (void-typed) foreign argument. + try std.testing.expectEqual(TypeId.void, emitter.getRefIRType(bogus) orelse TypeId.void); + + // Pass-after: the helper returns the dedicated `.unresolved` sentinel, + // never `.void`, so the failure cannot masquerade as a real type. + try std.testing.expectEqual(TypeId.unresolved, emitter.argIRTypeOrFail(bogus)); + try std.testing.expect(emitter.argIRTypeOrFail(bogus) != .void); +} diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 78853d2..0506f10 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -2239,6 +2239,16 @@ pub const LLVMEmitter = struct { return null; } + /// Resolve the IR type of a foreign-call argument ref. Every FFI arg ref is + /// a real function param or block instruction result, so a `null` here is a + /// codegen invariant violation, not a recoverable case: return the dedicated + /// `.unresolved` sentinel — never `.void`/`.s64` — so the failure cannot be + /// mistaken for a real type and trips `toLLVMType`'s hard tripwire at the call + /// site instead of silently emitting a void-typed foreign argument. + pub fn argIRTypeOrFail(self: *LLVMEmitter, arg_ref: Ref) TypeId { + return self.getRefIRType(arg_ref) orelse .unresolved; + } + /// Coerce both binary operands to match the instruction's result type. /// E.g. if result is i64 but one operand is i32, sext it. @@ -2460,7 +2470,7 @@ pub const LLVMEmitter = struct { call_args[1] = cls; call_args[2] = mid; for (msg.args, 0..) |arg_ref, i| { - const raw_ty = self.getRefIRType(arg_ref) orelse .void; + const raw_ty = self.argIRTypeOrFail(arg_ref); const raw_llvm = self.toLLVMType(raw_ty); const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm); call_param_types[i + 3] = coerced_ty; From 633c0a2540ebd71908f59fff1e0926c44f576547 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 15:55:32 +0300 Subject: [PATCH 25/26] =?UTF-8?q?docs(issues):=20file=200075=20=E2=80=94?= =?UTF-8?q?=20silent=20.s64=20type=20fallback=20in=20reflection=20builtins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discovered during the 0074 fix + a codebase-wide silent-type-fallback sweep. getRefIRType(...) orelse TypeId.s64 at ops.zig:1023/1049/1055 (type_name/type_eq). Blocker; to be resolved before the arch-refactor stream closes. --- ...75-reflection-builtin-s64-type-fallback.md | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 issues/0075-reflection-builtin-s64-type-fallback.md diff --git a/issues/0075-reflection-builtin-s64-type-fallback.md b/issues/0075-reflection-builtin-s64-type-fallback.md new file mode 100644 index 0000000..8e3c3b7 --- /dev/null +++ b/issues/0075-reflection-builtin-s64-type-fallback.md @@ -0,0 +1,77 @@ +# 0075 — silent `getRefIRType(...) orelse TypeId.s64` fallback in reflection builtins + +## Symptom +**One-line:** The `type_name` and `type_eq` reflection builtins resolve their Type +argument's IR type via `getRefIRType(...) orelse TypeId.s64` — the forbidden +silent-type-lookup fallback (`.s64` is the exact issue-0042 sentinel the project +rules name) — so a failed must-succeed lookup silently decides "not boxed (`!= .any`)" +and mis-handles the value with no diagnostic. + +**Observed (primary — must fix):** `self.e.getRefIRType(...) orelse TypeId.s64` at: +- `src/backend/llvm/ops.zig:1023` (`.type_name` builtin — `arg_ir_ty`, gates the + `== .any` boxed-extract vs bare-i64 decision) +- `src/backend/llvm/ops.zig:1049` (`.type_eq` builtin — first operand) +- `src/backend/llvm/ops.zig:1055` (`.type_eq` builtin — second operand) + +`getRefIRType` (`src/ir/emit_llvm.zig:2229`, `?TypeId`) returns `null` only when a ref +is neither a function param nor a block instruction result — a must-not-happen case +for a real builtin argument. On `null` the code defaults to `.s64`, then tests +`arg_ir_ty == .any`; the `.s64` default silently means "treat as a bare TypeId index, +not a boxed `Any`", so a genuinely-boxed arg whose lookup failed would skip the +`ExtractValue` and use the wrong value — silent miscompile, no diagnostic. + +**Expected:** per `CLAUDE.md` REJECTED PATTERNS, a failed must-succeed type lookup +surfaces a diagnostic / hard tripwire (e.g. the `.unresolved` sentinel introduced for +issue 0074), never a real-type default. + +## Secondary (confirm — borderline) +- `src/ir/lower.zig:2527` — `.null_literal => constNull(self.target_type orelse .void)` +- `src/ir/lower.zig:2528` — `.undef_literal => constUndef(self.target_type orelse .void)` + `target_type` is a context hint that may be legitimately absent for a bare + `null`/`undef` with no expected type — this may be an INTENTIONAL default rather + than a lookup-swallow. The fix session should confirm: if a `null`/`undef` literal + reaching here without a `target_type` is actually a must-not-happen case, make it + loud; if a typeless null/undef is legitimate, leave it and add a one-line comment + stating the invariant. + +## Audited — intentional language defaults (NO action; documented so they aren't re-flagged) +- `src/ir/lower.zig:4855` — `int_literal => constInt(lit.value, info.ty orelse .s64)`: + an untyped integer literal defaulting to `s64` is standard language semantics, not a + lookup failure. +- `src/ir/lower.zig:4856` — `float_literal => constFloat(..., info.ty orelse .f64)`: + untyped float literal defaults to `f64` — language semantics. +- `src/ir/type_bridge.zig:334` — `.tag_type = tag_type orelse .s64`: documented + ("enum unions are always tagged (default i64)") — an intentional default tag type, + not a swallowed lookup. + +## Provenance / scope +Pre-existing, NOT introduced by the arch-refactor. Discovered during the **issue-0074 +fix** (the fix worker surfaced the reflection `.s64` fallbacks as a separate pattern +outside 0074's FFI-arg scope) and confirmed by a manager sweep +(`rg "orelse \.(s64|void|...)" src`). Filed per the IMPASSIBLE RULE (existing +default-returns that swallow a lookup failure → file, don't fix in place). + +## Reproduction +Latent / static (same nature as 0074): well-formed IR always gives a builtin arg a +resolvable type, so the `.s64` default can't be driven at runtime today — which is why +it's dangerous (a future IR change would silently miscompile `type_name`/`type_eq`). +Exercised by the comptime/reflection examples; the fix must keep the suite at 361/0. + +## Investigation prompt (ready to paste into a fresh session) +> In `/Users/agra/projects/sx`, the `.type_name` and `.type_eq` reflection builtins in +> `src/backend/llvm/ops.zig` (lines 1023, 1049, 1055) resolve a Type argument's IR type +> with the forbidden silent fallback `getRefIRType(...) orelse TypeId.s64`, used to gate +> a `== .any` boxed-vs-bare decision. Issue 0074 already added the shared resolver +> `LLVMEmitter.argIRTypeOrFail` (`src/ir/emit_llvm.zig`) returning the dedicated +> `.unresolved` sentinel on a failed lookup. Route these three sites through that helper +> (or a sibling) so a failed lookup yields `.unresolved` — never `.s64`; then `==.any` +> is false for `.unresolved` AND you must make the unresolved case loud (diagnostic via +> `self.diagnostics.addFmt(.err, span, ...)` or a hard tripwire), not silently "bare +> i64". Also resolve the borderline `lower.zig:2527/2528` `target_type orelse .void` +> (confirm intentional vs make-loud; comment the invariant either way). Leave the +> audited intentional defaults (`lower.zig:4855/4856`, `type_bridge.zig:334`) untouched. +> Verify: `/Users/agra/.zvm/bin/zig build && /Users/agra/.zvm/bin/zig build test && +> bash tests/run_examples.sh` stays 361/0; add a `*.test.zig` regression test asserting +> the loud `.unresolved` path for a `type_name`/`type_eq` arg with an unresolvable ref +> (fail-before/pass-after). Expected new behavior: an unresolved reflection-builtin arg +> type surfaces loudly, never silently defaults to `.s64`. From aca077d72074ab6cf093b78d3fec650921d687d2 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 16:05:31 +0300 Subject: [PATCH 26/26] fix(reflection): replace silent `.s64` arg-type fallback with loud `.unresolved` (issue 0075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `type_name` / `type_eq` reflection builtins resolved their Type arg's IR type via `getRefIRType(...) orelse TypeId.s64`, then gated `== .any`. A failed must-succeed lookup silently became `.s64` (`!= .any`), classifying a boxed `Any` arg as bare i64 and reading the wrong value with no diagnostic. Add the sibling classifier `LLVMEmitter.reflectArgRepr`, which routes the lookup through `argIRTypeOrFail` (the issue-0074 `.unresolved` resolver) and returns `{ boxed, bare, unresolved }`. The three emit sites in ops.zig (`type_name` + `type_eq` x2) now switch on it: `.boxed` extracts the Any value field, `.bare` uses the value directly, `.unresolved` hits a hard `@panic` tripwire — never silently treated as bare. Real args always resolve, so the happy path is byte-identical (suite stays 361/0, zero snapshot churn). Secondary `lower.zig` `null_literal`/`undef_literal => target_type orelse .void` confirmed intentional (typeless-literal default deliberately handled by emitConstNull/emitConstUndef as null-ptr / undef-i64) — left with an invariant comment, not the `.unresolved` tripwire. Regression test in emit_llvm.test.zig asserts the loud path: fail-before with `orelse .s64` yields `.bare`; pass-after yields `.unresolved`. --- ...75-reflection-builtin-s64-type-fallback.md | 20 ++++++++ src/backend/llvm/ops.zig | 28 +++++----- src/ir/emit_llvm.test.zig | 51 +++++++++++++++++++ src/ir/emit_llvm.zig | 16 ++++++ src/ir/lower.zig | 4 ++ 5 files changed, 106 insertions(+), 13 deletions(-) diff --git a/issues/0075-reflection-builtin-s64-type-fallback.md b/issues/0075-reflection-builtin-s64-type-fallback.md index 8e3c3b7..e8d9818 100644 --- a/issues/0075-reflection-builtin-s64-type-fallback.md +++ b/issues/0075-reflection-builtin-s64-type-fallback.md @@ -1,3 +1,23 @@ +> **RESOLVED** (2026-06-03) +> **Root cause:** the `type_name` / `type_eq` reflection builtins resolved their +> `Type` arg's IR type with `getRefIRType(...) orelse TypeId.s64`, then gated `== .any` +> — so a failed must-succeed lookup silently became "bare i64" (`.s64 != .any`), +> reading the wrong value with no diagnostic. +> **Fix:** added the sibling classifier `LLVMEmitter.reflectArgRepr` +> (`src/ir/emit_llvm.zig`) which routes the lookup through `argIRTypeOrFail` → +> `.unresolved` and returns `{ boxed, bare, unresolved }`. The three emit sites +> (`src/backend/llvm/ops.zig` `type_name` + `type_eq` ×2) now `switch` on it: `.boxed` +> extracts the `Any` value field, `.bare` uses the value directly, and `.unresolved` +> hits a hard `@panic` tripwire — never silently classified as bare. Happy path +> (real args always resolve) is byte-identical; suite stays 361/0. +> **Secondary (confirmed intentional):** `src/ir/lower.zig:2531/2532` +> (`null_literal` / `undef_literal` → `target_type orelse .void`) is a typeless-literal +> default, not a lookup-swallow — `emitConstNull`/`emitConstUndef` deliberately handle +> `.void` (null-ptr / undef-i64). Left in place with an invariant comment. +> **Regression test:** `src/ir/emit_llvm.test.zig` — "emit: reflectArgRepr surfaces +> .unresolved for an unresolvable reflection arg ref (issue 0075)" (fail-before with +> `orelse .s64` → `.bare`; pass-after → `.unresolved`). + # 0075 — silent `getRefIRType(...) orelse TypeId.s64` fallback in reflection builtins ## Symptom diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index e2f529e..6542735 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -1020,14 +1020,12 @@ pub const Ops = struct { // index directly. const arg_ref = bi.args[0]; const arg_val = self.e.resolveRef(arg_ref); - const arg_ir_ty = self.e.getRefIRType(arg_ref) orelse TypeId.s64; - const tid_idx = blk: { - if (arg_ir_ty == .any) { - // Boxed: extract value field. - break :blk c.LLVMBuildExtractValue(self.e.builder, arg_val, 1, "tn.tid"); - } + const tid_idx = switch (self.e.reflectArgRepr(arg_ref)) { + .unresolved => @panic("type_name: reflection arg IR-type unresolved — a type-resolution failure reached LLVM emission without a diagnostic"), + // Boxed: extract value field from the Any aggregate. + .boxed => c.LLVMBuildExtractValue(self.e.builder, arg_val, 1, "tn.tid"), // Bare i64 (TypeId index). - break :blk arg_val; + .bare => arg_val, }; const arr_global = self.e.reflection().getOrBuildTypeNameArray(); const arr_len = self.e.type_name_array_len; @@ -1046,15 +1044,19 @@ pub const Ops = struct { // icmp eq. const a = blk: { const v = self.e.resolveRef(bi.args[0]); - const ty = self.e.getRefIRType(bi.args[0]) orelse TypeId.s64; - if (ty == .any) break :blk c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.a"); - break :blk v; + break :blk switch (self.e.reflectArgRepr(bi.args[0])) { + .unresolved => @panic("type_eq: first reflection arg IR-type unresolved — a type-resolution failure reached LLVM emission without a diagnostic"), + .boxed => c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.a"), + .bare => v, + }; }; const b = blk: { const v = self.e.resolveRef(bi.args[1]); - const ty = self.e.getRefIRType(bi.args[1]) orelse TypeId.s64; - if (ty == .any) break :blk c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.b"); - break :blk v; + break :blk switch (self.e.reflectArgRepr(bi.args[1])) { + .unresolved => @panic("type_eq: second reflection arg IR-type unresolved — a type-resolution failure reached LLVM emission without a diagnostic"), + .boxed => c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.b"), + .bare => v, + }; }; const eq_res = c.LLVMBuildICmp(self.e.builder, c.LLVMIntEQ, a, b, "te.eq"); self.e.mapRef(eq_res); diff --git a/src/ir/emit_llvm.test.zig b/src/ir/emit_llvm.test.zig index ff20cda..99c4e30 100644 --- a/src/ir/emit_llvm.test.zig +++ b/src/ir/emit_llvm.test.zig @@ -1144,3 +1144,54 @@ test "emit: argIRTypeOrFail surfaces .unresolved for an unresolvable FFI arg ref try std.testing.expectEqual(TypeId.unresolved, emitter.argIRTypeOrFail(bogus)); try std.testing.expect(emitter.argIRTypeOrFail(bogus) != .void); } + +// ── issue 0075: reflection-builtin arg-type lookup must fail loudly, never `.s64` ── +// `reflectArgRepr` backs the `type_name` / `type_eq` reflection builtins, which read +// their `Type` arg as a boxed `Any` aggregate (`.any` → extract value field) or a bare +// i64 TypeId index. A ref it cannot resolve is a codegen invariant violation; it must +// surface `.unresolved` (which the emit site hard-panics on) instead of the old silent +// `getRefIRType(arg) orelse .s64` default that would mis-classify a boxed arg as bare +// and read the wrong value with no diagnostic. +test "emit: reflectArgRepr surfaces .unresolved for an unresolvable reflection arg ref (issue 0075)" { + const alloc = std.testing.allocator; + var module = Module.init(alloc); + defer module.deinit(); + + var b = Builder.init(&module); + + // func reflfn(boxed: any, bare: s64) -> void { } + const fid = b.beginFunction(str(&module, "reflfn"), &[_]Function.Param{ + .{ .name = str(&module, "boxed"), .ty = .any }, + .{ .name = str(&module, "bare"), .ty = .s64 }, + }, .void); + const entry = b.appendBlock(str(&module, "entry"), &.{}); + b.switchToBlock(entry); + b.retVoid(); + b.finalize(); + + var emitter = LLVMEmitter.init(alloc, &module, "test_refl_argty", .{}); + defer emitter.deinit(); + emitter.current_func_idx = fid.index(); + + // Happy path: a boxed `.any` Type arg classifies as `.boxed` (extract value + // field); a bare `.s64` TypeId arg classifies as `.bare` (use directly). + // These decisions are byte-identical to the pre-fix `== .any` gate. + try std.testing.expectEqual(LLVMEmitter.ReflectArgRepr.boxed, emitter.reflectArgRepr(Ref.fromIndex(0))); + try std.testing.expectEqual(LLVMEmitter.ReflectArgRepr.bare, emitter.reflectArgRepr(Ref.fromIndex(1))); + + // A ref past every param and instruction is unresolvable. + const bogus = Ref.fromIndex(100_000); + try std.testing.expectEqual(@as(?TypeId, null), emitter.getRefIRType(bogus)); + + // Fail-before: the old `getRefIRType(arg) orelse .s64` would silently yield + // `.s64` here — which `!= .any`, so the reflection arm would treat a failed + // lookup as a bare i64 and read the wrong value with no diagnostic. + try std.testing.expectEqual(TypeId.s64, emitter.getRefIRType(bogus) orelse TypeId.s64); + try std.testing.expect((emitter.getRefIRType(bogus) orelse TypeId.s64) != .any); + + // Pass-after: the classifier returns the dedicated `.unresolved` variant, + // never `.bare`, so the emit site trips its hard panic instead of silently + // reading the wrong value. + try std.testing.expectEqual(LLVMEmitter.ReflectArgRepr.unresolved, emitter.reflectArgRepr(bogus)); + try std.testing.expect(emitter.reflectArgRepr(bogus) != .bare); +} diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 0506f10..183892c 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -2249,6 +2249,22 @@ pub const LLVMEmitter = struct { return self.getRefIRType(arg_ref) orelse .unresolved; } + /// How a reflection builtin (`type_name` / `type_eq`) must read its `Type` + /// argument: boxed inside an `Any` aggregate (extract the value field) vs a + /// bare i64 `TypeId` index. The IR-type lookup is must-succeed, so it routes + /// through `argIRTypeOrFail`; a failed lookup surfaces as `.unresolved` — + /// never a silent `.s64` that would mis-classify a boxed arg as bare and read + /// the wrong value. The caller turns `.unresolved` into a hard tripwire. + pub const ReflectArgRepr = enum { boxed, bare, unresolved }; + + pub fn reflectArgRepr(self: *LLVMEmitter, arg_ref: Ref) ReflectArgRepr { + return switch (self.argIRTypeOrFail(arg_ref)) { + .unresolved => .unresolved, + .any => .boxed, + else => .bare, + }; + } + /// Coerce both binary operands to match the instruction's result type. /// E.g. if result is i64 but one operand is i32, sext it. diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 6a84203..6bd5d98 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -2524,6 +2524,10 @@ pub const Lowering = struct { const sid = self.module.types.internString(str); break :blk self.builder.constString(sid); }, + // A bare `null` / `---` with no surrounding type expectation is a + // legitimate typeless literal, not a failed lookup: `.void` is its + // intentional default (emitConstNull/emitConstUndef handle void as + // null-ptr / undef-i64). Not a candidate for the `.unresolved` tripwire. .null_literal => self.builder.constNull(self.target_type orelse .void), .undef_literal => self.builder.constUndef(self.target_type orelse .void),