diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index 3695cc9..e6c4d8a 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -38,6 +38,8 @@ Perms :: enum flags { read; write; execute; } Status :: enum u8 { ok; err; timeout; } +WindowFlags :: enum flags u32 { vsync :: 64; resizable :: 4; hidden :: 128; } + // --- Top-level functions --- add :: (a: s32, b: s32) -> s32 { a + b; } @@ -72,13 +74,22 @@ early_return :: (x: s32) -> s32 { x; } -// #run compile-time constant -CT_VAL :: #run add(10, 15); +vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) { + .[x, y, z]; +} -// #insert helper +// #run compile-time constants +CT_VAL :: #run add(10, 15); +CT_MUL :: #run mul(6, 7); +CT_CHAIN :: #run add(CT_VAL, 5); + +// #insert helpers gen_code :: () -> string { return "print(\"insert-ok\\n\");"; } +gen_val :: () -> string { + return "print(\"insert-gen: {}\\n\", 42);"; +} // ============================================================ main :: { @@ -128,6 +139,18 @@ END; c : Color = .green; print("enum-lit: {}\n", c); + // Null pointer + np : *s32 = null; + print("null-ptr: {}\n", np); + + // String .len + slen := "hello"; + print("string-len: {}\n", slen.len); + + // Empty string .len + es := ""; + print("empty-string: {}\n", es.len); + // ======================================================== // 2. OPERATORS & PRECEDENCE // ======================================================== @@ -155,16 +178,37 @@ END; print("chain-gt: {}\n", 100 > v > 0); print("chain-mixed: {}\n", 100 > v >= 0); + // Equality chains + print("eq-chain: {}\n", 5 == 5 == 5); + print("eq-chain-f: {}\n", 5 == 5 == 6); + // Bitwise print("band: {}\n", 0xFF & 0x0F); print("bor: {}\n", 1 | 2 | 4); + // Bitwise on variables + bv1 := 0xFF; + bv2 := 0x0F; + print("band-var: {}\n", bv1 & bv2); + bv3 := 1; + bv4 := 6; + print("bor-var: {}\n", bv3 | bv4); + + // Modulo on variables + mv1 := 17; + mv2 := 5; + print("mod-var: {}\n", mv1 % mv2); + // Logical (short-circuit) print("and: {}\n", true and true); print("and-false: {}\n", true and false); print("or: {}\n", false or true); print("or-false: {}\n", false or false); + // Short-circuit verification + print("short-and: {}\n", false and true); + print("short-or: {}\n", true or false); + // Compound assignment ca := 10; ca += 5; @@ -185,6 +229,36 @@ END; small : u8 = xx big2; print("xx-cast: {}\n", small); + // Implicit widening conversions + wu : u8 = 200; + ws : s64 = wu; + print("widen-u8-s64: {}\n", ws); + + wi3 : s32 = 42; + wf : f64 = wi3; + print("widen-s32-f64: {}\n", wf); + + wf32 : f32 = 1.5; + wf64 : f64 = wf32; + print("widen-f32-f64: {}\n", wf64); + + wu2 : u8 = 100; + ws2 : s16 = wu2; + print("widen-u8-s16: {}\n", ws2); + + // More xx narrowing + xl : s64 = 12345; + xs : s32 = xx xl; + print("xx-s64-s32: {}\n", xs); + + xd : f64 = 1.5; + xf : f32 = xx xd; + print("xx-f64-f32: {}\n", xf); + + xdf : f64 = 7.9; + xdi : s32 = xx xdf; + print("xx-f64-s32: {}\n", xdi); + // ======================================================== // 3. TYPE SYSTEM // ======================================================== @@ -241,6 +315,13 @@ END; ec : Color = .red; print("enum: {}\n", ec); + // Enum comparison + ce1 : Color = .red; + ce2 : Color = .red; + ce3 : Color = .blue; + print("enum-eq: {}\n", ce1 == ce2); + print("enum-neq: {}\n", ce1 != ce3); + // Backing type st : Status = .err; print("backing: {}\n", st); @@ -257,6 +338,16 @@ END; sh = .none; print("void-variant: {}\n", sh); + // Variant reassignment + sh = .circle(1.0); + print("reassign: {}\n", sh); + sh = .rect(.{ 5, 3 }); + print("reassign2: {}\n", sh); + + // Type-prefix construction + tp := Shape.circle(2.5); + print("enum-prefix: {}\n", tp); + // Pattern matching sh2 : Shape = .rect(.{ 5, 3 }); if sh2 == { @@ -274,6 +365,15 @@ END; } print("match-expr: {}\n", ms); + // Match expression with else + me_val := 42; + me_res := if me_val == { + case 1: 10; + case 2: 20; + else: 99; + } + print("match-expr-else: {}\n", me_res); + // Payload capture (block form) sh4 : Shape = .circle(9.5); if sh4 == { @@ -282,6 +382,14 @@ END; case .none: print("capture: none\n"); } + // Payload capture (arrow form) + sh_ca : Shape = .circle(7.5); + if sh_ca == { + case .circle: (r) => print("capture-arrow: {}\n", r); + case .rect: (sz) => print("capture-arrow: rect\n"); + case .none: print("capture-arrow: none\n"); + } + // else arm in match num := 42; if num == { @@ -298,6 +406,26 @@ END; case 3: print("int-match: three\n"); } + // Integer match with else + im_code := 99; + if im_code == { + case 1: print("int-match-else: one\n"); + case 2: print("int-match-else: two\n"); + else: print("int-match-else: unknown\n"); + } + + // Bool pattern matching + bm := true; + if bm == { + case true: print("bool-match-t: yes\n"); + case false: print("bool-match-t: no\n"); + } + bm2 := false; + if bm2 == { + case true: print("bool-match-f: yes\n"); + case false: print("bool-match-f: no\n"); + } + // Bool conditional flag := true; if flag { print("bool: true\n"); } @@ -321,11 +449,21 @@ END; print("arr[2]: {}\n", arr[2]); print("arr.len: {}\n", arr.len); + // Array element assignment + aa : [3]s32 = .[1, 2, 3]; + aa[1] = 99; + print("arr-assign: {}\n", aa); + // --- Slices --- sl : []s32 = .[1, 2, 3, 4, 5]; print("sl[0]: {}\n", sl[0]); print("sl.len: {}\n", sl.len); + // Slice element write + sla : []s32 = .[10, 20, 30]; + sla[1] = 55; + print("sl-assign: {}\n", sla); + // Subslicing sub := arr[1..4]; print("sub: {}\n", sub); @@ -334,9 +472,17 @@ END; tail := arr[2..]; print("tail: {}\n", tail); + // Slice of slice + sos : []s32 = .[10, 20, 30, 40, 50]; + mid := sos[1..4]; + inner := mid[0..2]; + print("slice-of-slice: {}\n", inner); + // String subslicing msg := "hello world"; print("strsub: {}\n", msg[6..11]); + print("str-prefix: {}\n", msg[..5]); + print("str-suffix: {}\n", msg[6..]); // --- Pointers --- pv := Point.{ 10, 20 }; @@ -351,6 +497,32 @@ END; print("mp[0]: {}\n", mp[0]); print("mp[3]: {}\n", mp[3]); + // Many-pointer write + mpw : [5]s32 = .[10, 20, 30, 40, 50]; + mpw_ptr : [*]s32 = @mpw[0]; + mpw_ptr[2] = 99; + print("mp-write: {}\n", mpw[2]); + + // --- Vectors --- + vc := vec3(1, 3, 2); + print("vec-construct: {}\n", vc); + + va := vec3(1, 2, 3); + vb := vec3(4, 5, 6); + print("vec-add: {}\n", va + vb); + print("vec-sub: {}\n", vec3(5, 5, 5) - vec3(1, 2, 3)); + print("vec-mul: {}\n", vec3(2, 3, 4) * vec3(1, 2, 3)); + print("vec-div: {}\n", vec3(10, 9, 8) / vec3(2, 3, 4)); + + print("vec-scalar: {}\n", vec3(1, 3, 2) * 2.0); + print("vec-neg: {}\n", -vec3(1, 3, 2)); + + ve := vec3(10, 20, 30); + print("vec-x: {}\n", ve.x); + print("vec-y: {}\n", ve.y); + print("vec-z: {}\n", ve.z); + print("vec-idx: {}\n", ve[1]); + // ======================================================== // 4. CONTROL FLOW // ======================================================== @@ -360,11 +532,38 @@ END; ite := if true then 1 else 2; print("ite: {}\n", ite); + // If-then-else both branches + ie_a := if true then 10 else 20; + ie_b := if false then 10 else 20; + print("ite-both: {} {}\n", ie_a, ie_b); + // If block if 1 < 2 { print("if-block: yes\n"); } + // If without else (statement) + if false { print("should-not-print\n"); } + print("if-no-else: after\n"); + + // Nested if + nx := 10; + if nx > 5 { + if nx > 8 { + print("nested-if: deep\n"); + } + } + + // If-else-if chain + eiv := 2; + if eiv == 1 { + print("if-else-if: first\n"); + } else if eiv == 2 { + print("if-else-if: second\n"); + } else { + print("if-else-if: other\n"); + } + // If block as expression ibe := 10 + if true { 5; } else { 0; }; print("if-block-expr: {}\n", ibe); @@ -374,6 +573,10 @@ END; while wi < 5 { wi += 1; } print("while: {}\n", wi); + // While with false condition (never executes) + while false { print("should-not-print\n"); } + print("while-false: skipped\n"); + // While with break wb := 0; while wb < 100 { @@ -392,7 +595,44 @@ END; } print("while-continue: {}\n", wsum); - // For loop basic (using write like example 19) + // While sum 1..10 + wsum2 := 0; + wi2 := 1; + while wi2 <= 10 { + wsum2 += wi2; + wi2 += 1; + } + print("while-sum: {}\n", wsum2); + + // Nested while + nw_outer := 0; + nw_count := 0; + while nw_outer < 3 { + nw_inner := 0; + while nw_inner < 3 { + nw_count += 1; + nw_inner += 1; + } + nw_outer += 1; + } + print("nested-while: {}\n", nw_count); + + // Nested while with break in inner + nb_outer := 0; + nb_icount := 0; + while nb_outer < 5 { + nb_i := 0; + while nb_i < 5 { + if nb_i == 1 { break; } + nb_i += 1; + } + nb_icount += nb_i; + nb_outer += 1; + if nb_outer == 2 { break; } + } + print("nested-break: {} {}\n", nb_outer, nb_icount); + + // For loop basic farr : [4]s32 = .[10, 20, 30, 40]; write("for:"); for farr { @@ -439,6 +679,44 @@ END; } write("\n"); + // For on slice + fsl : []s32 = .[10, 20, 30]; + write("for-slice:"); + for fsl { + print(" {}", it); + } + write("\n"); + + // For on slice with it_index + write("for-slice-idx:"); + for fsl { + print(" {}:{}", it_index, it); + } + write("\n"); + + // Nested for + nf_a : [2]s32 = .[0, 1]; + nf_b : [2]s32 = .[0, 1]; + write("for-nested:"); + for nf_a { + oa := it; + for nf_b { + print(" ({},{})", oa, it); + } + } + write("\n"); + + // For with break preserving it_index + fbi : [5]s32 = .[10, 20, 30, 40, 50]; + fbi_idx := 0; + for fbi { + if it == 30 { fbi_idx = it_index; break; } + } + print("for-break-idx: {}\n", fbi_idx); + + // Multiple print placeholders + print("multi: {} {} {}\n", 1, 2, 3); + // ======================================================== // 5. FUNCTIONS & DECLARATIONS // ======================================================== @@ -470,6 +748,7 @@ END; // Generic — single param print("generic-s32: {}\n", identity(42)); print("generic-f32: {}\n", identity(1.5)); + print("generic-bool: {}\n", identity(true)); // Generic — multiple params print("generic-multi: {}\n", pair_add(10, 20)); @@ -482,6 +761,13 @@ END; halve :: (x: f32) -> f32 => x / 2.0; print("lambda-ret: {}\n", halve(10.0)); + // Local function (non-lambda) + local_add :: (a: s32, b: s32) -> s32 { a + b; } + print("local-fn: {}\n", local_add(3, 4)); + + // Nested function calls + print("fn-nested: {}\n", add(mul(2, 3), mul(4, 5))); + // Variadic (typed) print("varargs: {}\n", typed_sum(1, 2, 3, 4, 5)); @@ -509,6 +795,14 @@ END; } print("outer: {}\n", sv); + // Shadow with different type + st_v := 42; + print("shadow-type: {}\n", st_v); + { + st_v := 3.14; + print("shadow-type: {}\n", st_v); + } + // Nested scopes (3 levels) nv := 1; { @@ -521,6 +815,15 @@ END; } print("nest1: {}\n", nv); + // Scope isolation + { iso := 100; print("scope-isolate: {}\n", iso); } + + // Reuse name after scope exit + sr := 1; + print("scope-reuse: {}\n", sr); + { sr := 2; print("scope-reuse: {}\n", sr); } + print("scope-reuse: {}\n", sr); + // Multiple defers (LIFO order) { defer print("defer-c\n"); @@ -528,6 +831,14 @@ END; defer print("defer-a\n"); } + // Four defers + { + defer print("d1\n"); + defer print("d2\n"); + defer print("d3\n"); + defer print("d4\n"); + } + // Defer in nested scopes { defer print("outer-defer\n"); @@ -536,6 +847,12 @@ END; } } + // Defer in if block + if true { + defer print("defer-in-if: deferred\n"); + print("defer-in-if: body\n"); + } + // ======================================================== // 7. BUILT-IN FUNCTIONS // ======================================================== @@ -546,10 +863,12 @@ END; // sqrt print("sqrt: {}\n", sqrt(9.0)); + print("sqrt-f64: {}\n", sqrt(16.0)); // size_of print("sizeof-s32: {}\n", size_of(s32)); print("sizeof-f64: {}\n", size_of(f64)); + print("sizeof-struct: {}\n", size_of(Point)); // type_of + category matching tv := 42; @@ -560,16 +879,58 @@ END; else: print("typeof: other\n"); } + // type_of — float + tf := 3.14; + if type_of(tf) == { + case float: print("typeof-float: float\n"); + else: print("typeof-float: other\n"); + } + + // type_of — string + ts := "hello"; + if type_of(ts) == { + case string: print("typeof-string: string\n"); + else: print("typeof-string: other\n"); + } + + // type_of — bool + tb := true; + if type_of(tb) == { + case bool: print("typeof-bool: bool\n"); + else: print("typeof-bool: other\n"); + } + + // type_of — struct + tst := Point.{ 1, 2 }; + if type_of(tst) == { + case struct: print("typeof-struct: struct\n"); + else: print("typeof-struct: other\n"); + } + + // type_of — enum + ten : Color = .red; + if type_of(ten) == { + case enum: print("typeof-enum: enum\n"); + else: print("typeof-enum: other\n"); + } + // type_name print("typename: {}\n", type_name(Point)); - // field_count + // field_count on struct print("fieldcount: {}\n", field_count(Point)); - // field_name + // field_count on enum + print("fieldcount-enum: {}\n", field_count(Color)); + + // field_name on struct print("fieldname0: {}\n", field_name(Point, 0)); print("fieldname1: {}\n", field_name(Point, 1)); + // field_name on enum + print("fieldname-enum0: {}\n", field_name(Color, 0)); + print("fieldname-enum2: {}\n", field_name(Color, 2)); + // field_value (use any_to_string to avoid sext-on-Any bug) fv_pt := Point.{ 11, 22 }; write("fieldval0: "); @@ -579,13 +940,21 @@ END; write(any_to_string(field_value(fv_pt, 1))); write("\n"); - // field_index on enum + // field_index on plain enum fi_c : Color = .green; print("fieldidx: {}\n", field_index(Color, fi_c)); + // field_index on tagged enum + fi_sh : Shape = .circle(1.0); + print("fieldidx-tagged: {}\n", field_index(Shape, fi_sh)); + fi_sh2 : Shape = .none; + print("fieldidx-tagged2: {}\n", field_index(Shape, fi_sh2)); + // cast cval : f64 = 3.7; print("cast: {}\n", cast(s32) cval); + cv2 : s32 = 42; + print("cast-int-f64: {}\n", cast(f64) cv2); // ======================================================== // 8. COMPILE-TIME @@ -595,9 +964,18 @@ END; // #run constant print("run-const: {}\n", CT_VAL); + // #run with expression + print("run-expr: {}\n", CT_MUL); + + // #run chained dependency + print("run-chain: {}\n", CT_CHAIN); + // #insert with function #insert gen_code(); + // #insert additional + #insert gen_val(); + // ======================================================== // 9. FLAGS // ======================================================== @@ -611,8 +989,29 @@ END; if perm & .read { print("has-read: yes\n"); } if perm & .execute { print("has-exec: yes\n"); } + // Test flag negative + pt : Perms = .write; + if pt & .read { + print("flags-neg: has-read\n"); + } else { + print("flags-neg: no-read\n"); + } + + // Single flag + ps : Perms = .execute; + print("flags-single: {}\n", ps); + + // All flags + pall : Perms = .read | .write | .execute; + print("flags-all: {}\n", pall); + // Cast to int print("flags-raw: {}\n", cast(s64) perm); + // Flags with explicit values + wf : WindowFlags = .vsync | .resizable; + print("flags-explicit: {}\n", wf); + print("flags-explicit-raw: {}\n", cast(s64) wf); + print("=== DONE ===\n"); } diff --git a/src/codegen.zig b/src/codegen.zig index eae543d..2b95dc8 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -104,6 +104,12 @@ pub const CodeGen = struct { module: c.LLVMModuleRef, builder: c.LLVMBuilderRef, allocator: std.mem.Allocator, + // ORC ThreadSafeContext — wraps the LLVMContext for JIT compatibility + ts_context: c.LLVMOrcThreadSafeContextRef = null, + // Whether we still own the module (false after JIT takes ownership) + module_owned: bool = true, + // Cached target machine (created in init, reused by emitToFile) + target_machine: c.LLVMTargetMachineRef = null, // Symbol table: maps variable names to their alloca pointers named_values: std.StringHashMap(NamedValue), @@ -188,6 +194,16 @@ pub const CodeGen = struct { function_return_types: std.StringHashMap(Type), // Target configuration (triple, cpu, opt level, lib paths, linker) target_config: TargetConfig = .{}, + // Cached primitive LLVM types (initialized once in init(), avoids repeated FFI calls) + cached_i1: c.LLVMTypeRef = null, + cached_i8: c.LLVMTypeRef = null, + cached_i16: c.LLVMTypeRef = null, + cached_i32: c.LLVMTypeRef = null, + cached_i64: c.LLVMTypeRef = null, + cached_f32: c.LLVMTypeRef = null, + cached_f64: c.LLVMTypeRef = null, + cached_ptr: c.LLVMTypeRef = null, + cached_void: c.LLVMTypeRef = null, const DeferredFn = struct { fd: ast.FnDecl, @@ -311,12 +327,18 @@ pub const CodeGen = struct { }; pub fn init(allocator: std.mem.Allocator, module_name: [*:0]const u8, target_config: TargetConfig) CodeGen { - const ctx = c.LLVMContextCreate(); + // Create context via ORC ThreadSafeContext for JIT compatibility + const ts_ctx = c.LLVMOrcCreateNewThreadSafeContext(); + const ctx = c.LLVMOrcThreadSafeContextGetContext(ts_ctx); const module = c.LLVMModuleCreateWithNameInContext(module_name, ctx); const builder = c.LLVMCreateBuilderInContext(ctx); - // Initialize LLVM targets and set data layout early so alignment queries work - llvm.initAllTargets(); + // Initialize LLVM targets — native-only when targeting host, all for cross-compilation + if (target_config.triple == null) { + llvm.initNativeTarget(); + } else { + llvm.initAllTargets(); + } const triple_owned = target_config.triple == null; const triple = target_config.triple orelse c.LLVMGetDefaultTargetTriple(); @@ -326,8 +348,9 @@ pub const CodeGen = struct { var target: c.LLVMTargetRef = null; var err_msg: [*c]u8 = null; + var tm: c.LLVMTargetMachineRef = null; if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) == 0) { - const tm = c.LLVMCreateTargetMachine( + tm = c.LLVMCreateTargetMachine( target, triple, target_config.getCpu(), @@ -339,7 +362,6 @@ pub const CodeGen = struct { const dl = c.LLVMCreateTargetDataLayout(tm); c.LLVMSetModuleDataLayout(module, dl); c.LLVMDisposeTargetData(dl); - c.LLVMDisposeTargetMachine(tm); } else { if (err_msg != null) c.LLVMDisposeMessage(err_msg); } @@ -348,6 +370,8 @@ pub const CodeGen = struct { .module = module, .builder = builder, .allocator = allocator, + .ts_context = ts_ctx, + .target_machine = tm, .named_values = std.StringHashMap(NamedValue).init(allocator), .type_registry = std.StringHashMap(TypeRegistryEntry).init(allocator), .flags_enum_types = std.StringHashMap(void).init(allocator), @@ -375,6 +399,15 @@ pub const CodeGen = struct { .global_mutable_vars = std.StringHashMap(NamedValue).init(allocator), .function_return_types = std.StringHashMap(Type).init(allocator), .target_config = target_config, + .cached_i1 = c.LLVMInt1TypeInContext(ctx), + .cached_i8 = c.LLVMInt8TypeInContext(ctx), + .cached_i16 = c.LLVMInt16TypeInContext(ctx), + .cached_i32 = c.LLVMInt32TypeInContext(ctx), + .cached_i64 = c.LLVMInt64TypeInContext(ctx), + .cached_f32 = c.LLVMFloatTypeInContext(ctx), + .cached_f64 = c.LLVMDoubleTypeInContext(ctx), + .cached_ptr = c.LLVMPointerTypeInContext(ctx, 0), + .cached_void = c.LLVMVoidTypeInContext(ctx), }; } @@ -396,8 +429,15 @@ pub const CodeGen = struct { self.foreign_libraries.deinit(self.allocator); self.foreign_fns.deinit(); c.LLVMDisposeBuilder(self.builder); - c.LLVMDisposeModule(self.module); - c.LLVMContextDispose(self.context); + if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm); + if (self.module_owned) { + c.LLVMDisposeModule(self.module); + } + if (self.ts_context) |ts_ctx| { + c.LLVMOrcDisposeThreadSafeContext(ts_ctx); + } else { + c.LLVMContextDispose(self.context); + } } fn getStructInfo(self: *CodeGen, name: []const u8) !StructInfo { @@ -510,8 +550,14 @@ pub const CodeGen = struct { pub fn typeToLLVM(self: *CodeGen, ty: Type) c.LLVMTypeRef { return switch (ty) { - .signed => |w| c.LLVMIntTypeInContext(self.context, w), - .unsigned => |w| c.LLVMIntTypeInContext(self.context, w), + .signed, .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.f32Type(), .f64 => self.f64Type(), .void_type => self.voidType(), @@ -736,15 +782,15 @@ pub const CodeGen = struct { return self.buildFatPointer(self.getStringStructType(), ptr, len_val); } - // LLVM type shortcuts - fn i1Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt1TypeInContext(self.context); } - fn i8Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt8TypeInContext(self.context); } - fn i32Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt32TypeInContext(self.context); } - fn i64Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt64TypeInContext(self.context); } - fn f32Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMFloatTypeInContext(self.context); } - fn f64Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMDoubleTypeInContext(self.context); } - fn ptrType(self: *CodeGen) c.LLVMTypeRef { return c.LLVMPointerTypeInContext(self.context, 0); } - fn voidType(self: *CodeGen) c.LLVMTypeRef { return c.LLVMVoidTypeInContext(self.context); } + // LLVM type shortcuts (cached — no FFI call) + fn i1Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i1.?; } + fn i8Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i8.?; } + fn i32Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i32.?; } + fn i64Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i64.?; } + fn f32Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_f32.?; } + fn f64Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_f64.?; } + fn ptrType(self: *CodeGen) c.LLVMTypeRef { return self.cached_ptr.?; } + fn voidType(self: *CodeGen) c.LLVMTypeRef { return self.cached_void.?; } fn gepArrayElement(self: *CodeGen, arr_ty: c.LLVMTypeRef, arr_ptr: c.LLVMValueRef, idx: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { var indices = [_]c.LLVMValueRef{ self.constInt32(0), idx }; @@ -6206,13 +6252,16 @@ pub const CodeGen = struct { // Merge block self.positionAt(merge_bb); - // PHI node if both branches produced values + // PHI node if both branches produced values (skip for void type) if (then_val != null and else_val != null) { - const phi = c.LLVMBuildPhi(self.builder, c.LLVMTypeOf(then_val), "iftmp"); - var vals = [2]c.LLVMValueRef{ then_val, else_val }; - var blocks = [2]c.LLVMBasicBlockRef{ then_bb, else_bb }; - c.LLVMAddIncoming(phi, &vals, &blocks, 2); - return phi; + const ty = c.LLVMTypeOf(then_val); + if (c.LLVMGetTypeKind(ty) != c.LLVMVoidTypeKind) { + const phi = c.LLVMBuildPhi(self.builder, ty, "iftmp"); + var vals = [2]c.LLVMValueRef{ then_val, else_val }; + var blocks = [2]c.LLVMBasicBlockRef{ then_bb, else_bb }; + c.LLVMAddIncoming(phi, &vals, &blocks, 2); + return phi; + } } return null; @@ -7176,39 +7225,12 @@ pub const CodeGen = struct { } fn emitToFile(self: *CodeGen, output_path: [*:0]const u8, file_type: c.LLVMCodeGenFileType) !void { - llvm.initAllTargets(); + const tm = self.target_machine orelse return self.emitError("no target machine available"); - const cfg = self.target_config; - const triple_owned = cfg.triple == null; - const triple = cfg.triple orelse c.LLVMGetDefaultTargetTriple(); - defer if (triple_owned) c.LLVMDisposeMessage(@constCast(triple)); - - var target: c.LLVMTargetRef = null; var err_msg: [*c]u8 = null; - - if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) != 0) { + if (c.LLVMTargetMachineEmitToFile(tm, self.module, output_path, file_type, &err_msg) != 0) { defer c.LLVMDisposeMessage(err_msg); const msg = std.mem.span(err_msg); - return self.emitErrorFmt("failed to get target: {s}", .{msg}); - } - - const tm = c.LLVMCreateTargetMachine( - target, - triple, - cfg.getCpu(), - cfg.getFeatures(), - cfg.opt_level.toLLVM(), - c.LLVMRelocPIC, - c.LLVMCodeModelDefault, - ); - defer c.LLVMDisposeTargetMachine(tm); - - c.LLVMSetTarget(self.module, triple); - - var err_msg2: [*c]u8 = null; - if (c.LLVMTargetMachineEmitToFile(tm, self.module, output_path, file_type, &err_msg2) != 0) { - defer c.LLVMDisposeMessage(err_msg2); - const msg = std.mem.span(err_msg2); return self.emitErrorFmt("failed to emit file: {s}", .{msg}); } } @@ -7221,6 +7243,75 @@ pub const CodeGen = struct { return self.emitToFile(output_path, c.LLVMAssemblyFile); } + /// Emit the module as an object file to a memory buffer. + /// Caller owns the returned buffer and must dispose or pass to JIT. + pub fn emitObjectToMemory(self: *CodeGen) !c.LLVMMemoryBufferRef { + const tm = self.target_machine orelse return self.emitError("no target machine available"); + var err_msg: [*c]u8 = null; + var buf: c.LLVMMemoryBufferRef = null; + if (c.LLVMTargetMachineEmitToMemoryBuffer(tm, self.module, c.LLVMObjectFile, &err_msg, &buf) != 0) { + if (err_msg != null) { + defer c.LLVMDisposeMessage(err_msg); + const msg = std.mem.span(err_msg); + return self.emitErrorFmt("failed to emit object to memory: {s}", .{msg}); + } + return error.CompileError; + } + return buf; + } + + /// Execute a precompiled object file in-process using LLVM's ORC JIT. + /// Takes ownership of obj_buf. Returns the exit code from main(). + pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 { + // Create LLJIT with default builder (no custom TM needed — .o is precompiled) + var jit: c.LLVMOrcLLJITRef = null; + var err = c.LLVMOrcCreateLLJIT(&jit, null); + if (err != null) { + const msg = c.LLVMGetErrorMessage(err); + defer c.LLVMDisposeErrorMessage(msg); + std.debug.print("JIT error: {s}\n", .{std.mem.span(msg)}); + return error.CompileError; + } + defer _ = c.LLVMOrcDisposeLLJIT(jit); + + // Add process symbols so JIT can find libc (printf, write, etc.) + const jd = c.LLVMOrcLLJITGetMainJITDylib(jit); + const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit); + var gen: c.LLVMOrcDefinitionGeneratorRef = null; + err = c.LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(&gen, prefix, null, null); + if (err != null) { + const msg = c.LLVMGetErrorMessage(err); + defer c.LLVMDisposeErrorMessage(msg); + std.debug.print("JIT symbol gen error: {s}\n", .{std.mem.span(msg)}); + return error.CompileError; + } + c.LLVMOrcJITDylibAddGenerator(jd, gen); + + // Add precompiled object file (transfers ownership of obj_buf) + err = c.LLVMOrcLLJITAddObjectFile(jit, jd, obj_buf); + if (err != null) { + const msg = c.LLVMGetErrorMessage(err); + defer c.LLVMDisposeErrorMessage(msg); + std.debug.print("JIT add object error: {s}\n", .{std.mem.span(msg)}); + return error.CompileError; + } + + // Look up the "main" function + var main_addr: c.LLVMOrcExecutorAddress = 0; + err = c.LLVMOrcLLJITLookup(jit, &main_addr, "main"); + if (err != null) { + const msg = c.LLVMGetErrorMessage(err); + defer c.LLVMDisposeErrorMessage(msg); + std.debug.print("JIT lookup error: {s}\n", .{std.mem.span(msg)}); + return error.CompileError; + } + + // Cast to function pointer and call + const main_fn: *const fn () callconv(.c) i32 = @ptrFromInt(main_addr); + const result = main_fn(); + return if (result >= 0 and result <= 255) @intCast(result) else 1; + } + pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, output_bin: []const u8, libraries: []const []const u8, target_config: TargetConfig) !void { var argv = std.ArrayList([]const u8).empty; diff --git a/src/llvm_api.zig b/src/llvm_api.zig index 6a2c701..078b04d 100644 --- a/src/llvm_api.zig +++ b/src/llvm_api.zig @@ -1,7 +1,7 @@ pub const c = @cImport({ @cInclude("llvm-c/Core.h"); @cInclude("llvm-c/Analysis.h"); - @cInclude("llvm-c/BitWriter.h"); + @cInclude("llvm-c/Target.h"); @cInclude("llvm-c/TargetMachine.h"); @cInclude("llvm-c/LLJIT.h"); diff --git a/src/main.zig b/src/main.zig index 20460eb..0567668 100644 --- a/src/main.zig +++ b/src/main.zig @@ -23,6 +23,9 @@ pub fn main(init: std.process.Init) !void { var input_path: ?[]const u8 = null; var target_config = sx.codegen.TargetConfig{}; var lib_paths = std.ArrayList([]const u8).empty; + var show_timing: bool = false; + var explicit_opt: bool = false; + var no_cache: bool = false; var i: usize = 2; while (i < args.len) : (i += 1) { @@ -42,6 +45,7 @@ pub fn main(init: std.process.Init) !void { std.debug.print("error: invalid --opt value '{s}' (expected: none/0, less/1, default/2, aggressive/3)\n", .{args[i]}); return; }; + explicit_opt = true; } else if (std.mem.eql(u8, arg, "-o")) { i += 1; if (i >= args.len) { std.debug.print("error: -o requires a value\n", .{}); return; } @@ -54,6 +58,10 @@ pub fn main(init: std.process.Init) !void { i += 1; if (i >= args.len) { std.debug.print("error: --sysroot requires a value\n", .{}); return; } target_config.sysroot = args[i]; + } else if (std.mem.eql(u8, arg, "--time")) { + show_timing = true; + } else if (std.mem.eql(u8, arg, "--no-cache")) { + no_cache = true; } else if (std.mem.startsWith(u8, arg, "-L")) { if (arg.len > 2) { try lib_paths.append(allocator, arg[2..]); @@ -79,33 +87,84 @@ pub fn main(init: std.process.Init) !void { if (std.mem.eql(u8, command, "build")) { const output_name = target_config.output_path orelse deriveOutputName(path); - compile(allocator, io, path, output_name, target_config) catch return; + compile(allocator, io, path, output_name, target_config, show_timing, no_cache) catch return; std.debug.print("compiled: {s}\n", .{output_name}); } else if (std.mem.eql(u8, command, "ir")) { emitIR(allocator, io, path, target_config) catch return; } else if (std.mem.eql(u8, command, "asm")) { emitAsm(allocator, io, path, target_config) catch return; } else if (std.mem.eql(u8, command, "run")) { - const tmp_bin = if (comptime @import("builtin").os.tag == .windows) "sx_run_tmp.exe" else "/tmp/sx_run_tmp"; - compile(allocator, io, path, tmp_bin, target_config) catch return; - defer { - std.Io.Dir.deleteFile(.cwd(), io, tmp_bin) catch {}; - } - var child = std.process.spawn(io, .{ - .argv = &.{tmp_bin}, - }) catch { - std.debug.print("error: failed to run program\n", .{}); + // Default to -O0 for run (faster compile) unless user explicitly set --opt + if (!explicit_opt) target_config.opt_level = .none; + var timer = Timing.init(show_timing); + + // Phase A: read + parse + resolveImports (for cache key) + timer.mark(); + const source = readSource(allocator, io, path) catch return; + timer.record("read"); + + var comp = sx.core.Compilation.init(allocator, io, path, source, target_config); + defer comp.deinit(); + + timer.mark(); + comp.parse() catch { comp.renderErrors(); return; }; + timer.record("parse"); + + timer.mark(); + comp.resolveImports() catch { comp.renderErrors(); return; }; + timer.record("imports"); + + // Cache check — use .o files (precompiled object, skip IR compilation in JIT) + // Disable caching for files with top-level #run (side effects lost on cache hit) + const root = comp.resolved_root orelse comp.root orelse return; + const use_cache = !no_cache and !hasTopLevelRun(root); + const key = computeCacheKey(source, &comp.import_sources, target_config); + const cache_obj = cachePath(allocator, key, "o") catch return; + + timer.mark(); + const obj_buf: sx.llvm_api.c.LLVMMemoryBufferRef = blk: { + if (use_cache) { + // Try loading cached .o from disk + var buf: sx.llvm_api.c.LLVMMemoryBufferRef = null; + var err_msg: [*c]u8 = null; + if (sx.llvm_api.c.LLVMCreateMemoryBufferWithContentsOfFile(cache_obj.ptr, &buf, &err_msg) == 0) { + timer.record("cache"); + break :blk buf; + } + if (err_msg != null) sx.llvm_api.c.LLVMDisposeMessage(err_msg); + } + + // Cache MISS — codegen + emit .o to memory (verify skipped: JIT catches errors) + comp.generateCode() catch { comp.renderErrors(); return; }; + timer.record("codegen"); + + timer.mark(); + var cg = &comp.cg.?; + const buf = cg.emitObjectToMemory() catch { comp.renderErrors(); return; }; + timer.record("emit"); + + // Save .o to cache (extract data before JIT takes ownership) + if (use_cache) { + saveObjectToCache(buf, io, cache_obj); + } + + break :blk buf; + }; + + // JIT from precompiled object (relocation only, no IR compilation) + sx.llvm_api.initNativeTarget(); + timer.mark(); + const exit_code = sx.codegen.CodeGen.runJITFromObject(obj_buf) catch { + // JIT failed — fall back to AOT + timer.record("jit-fail"); + runAOT(allocator, io, path, target_config, &timer, no_cache) catch return; + timer.printAll(); return; }; - const term = child.wait(io) catch { - std.debug.print("error: program execution failed\n", .{}); - return; - }; - switch (term) { - .exited => |code| if (code != 0) std.process.exit(code), - .signal => std.process.exit(1), - .stopped, .unknown => std.process.exit(1), - } + timer.record("jit"); + timer.printAll(); + + if (exit_code != 0) std.process.exit(exit_code); } else { printUsage(); } @@ -138,6 +197,8 @@ fn printUsage() void { \\ -L Library search path (repeatable) \\ --linker Linker command (default: cc) \\ --sysroot Sysroot for cross-compilation + \\ --no-cache Disable build caching + \\ --time Show compilation timing breakdown \\ , .{}); } @@ -191,30 +252,44 @@ fn readSource(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) return try allocator.dupeZ(u8, source_bytes); } -fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !sx.core.Compilation { +fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing) !sx.core.Compilation { + timer.mark(); const source = try readSource(allocator, io, input_path); + timer.record("read"); var comp = sx.core.Compilation.init(allocator, io, input_path, source, target_config); errdefer comp.deinit(); + timer.mark(); comp.parse() catch { comp.renderErrors(); return error.CompileError; }; - comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; }; - comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("parse"); + timer.mark(); + comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("imports"); + + timer.mark(); + comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("codegen"); + + timer.mark(); var cg = &comp.cg.?; cg.verify() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("verify"); return comp; } fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void { - var comp = try compilePipeline(allocator, io, input_path, target_config); + var timer = Timing.init(false); + var comp = try compilePipeline(allocator, io, input_path, target_config, &timer); defer comp.deinit(); comp.cg.?.printIR(); } fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void { - var comp = try compilePipeline(allocator, io, input_path, target_config); + var timer = Timing.init(false); + var comp = try compilePipeline(allocator, io, input_path, target_config, &timer); defer comp.deinit(); const asm_path = target_config.output_path orelse blk: { const name = deriveOutputName(input_path); @@ -225,22 +300,236 @@ fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, tar std.debug.print("emitted: {s}\n", .{asm_path}); } -fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig) !void { - var comp = try compilePipeline(allocator, io, input_path, target_config); +fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, show_timing: bool, no_cache: bool) !void { + var timer = Timing.init(show_timing); + try compileWithTimer(allocator, io, input_path, output_path, target_config, &timer, no_cache); + timer.printAll(); +} + +fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, no_cache: bool) !void { + // Phase A: read + parse + resolveImports (fast: ~0.5ms) + timer.mark(); + const source = try readSource(allocator, io, input_path); + timer.record("read"); + + var comp = sx.core.Compilation.init(allocator, io, input_path, source, target_config); + errdefer comp.deinit(); defer comp.deinit(); - var cg = &comp.cg.?; + timer.mark(); + comp.parse() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("parse"); + + timer.mark(); + comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("imports"); + + // Extract library names from AST (needed for linking regardless of cache) + const root = comp.resolved_root orelse comp.root orelse return error.CompileError; + const libs = try extractLibraries(allocator, root); - // Emit object file const obj_path = try std.fmt.allocPrintSentinel(allocator, "{s}.o", .{output_path}, 0); - cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; }; + + // Cache: compute key and check for cached binary/.o + const key = computeCacheKey(source, &comp.import_sources, target_config); + const cache_obj = try cachePath(allocator, key, "o"); + const cache_bin = try cachePath(allocator, key, "bin"); + + // Level 1: Try cached binary (skip everything — no codegen, no link) + if (!no_cache) bin_cache: { + std.Io.Dir.copyFile(.cwd(), cache_bin, .cwd(), output_path, io, .{}) catch break :bin_cache; + timer.record("cache"); + return; + } + + // Level 2: Try cached .o (skip codegen+emit, still need link) + const used_obj_cache = blk: { + if (no_cache) break :blk false; + std.Io.Dir.copyFile(.cwd(), cache_obj, .cwd(), obj_path, io, .{}) catch break :blk false; + break :blk true; + }; + + if (used_obj_cache) { + timer.record("cache"); + } else { + // Cache MISS — full codegen + emit + timer.mark(); + comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("codegen"); + + timer.mark(); + var cg = &comp.cg.?; + cg.verify() catch { comp.renderErrors(); return error.CompileError; }; + timer.record("verify"); + + timer.mark(); + cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; }; + timer.record("emit"); + + // Save .o to cache + if (!no_cache) { + std.Io.Dir.copyFile(.cwd(), obj_path, .cwd(), cache_obj, io, .{ .make_path = true }) catch {}; + } + } // Link - sx.codegen.CodeGen.link(allocator, io, obj_path, output_path, cg.foreign_libraries.items, target_config) catch { + timer.mark(); + sx.codegen.CodeGen.link(allocator, io, obj_path, output_path, libs, target_config) catch { std.debug.print("error: linking failed\n", .{}); return error.CompileError; }; + timer.record("link"); + + // Save linked binary to cache + if (!no_cache) { + std.Io.Dir.copyFile(.cwd(), output_path, .cwd(), cache_bin, io, .{ .make_path = true }) catch {}; + } // Clean up object file std.Io.Dir.deleteFile(.cwd(), io, obj_path) catch {}; } + +fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, no_cache: bool) !void { + const tmp_bin = if (comptime @import("builtin").os.tag == .windows) "sx_run_tmp.exe" else "/tmp/sx_run_tmp"; + try compileWithTimer(allocator, io, input_path, tmp_bin, target_config, timer, no_cache); + defer { + std.Io.Dir.deleteFile(.cwd(), io, tmp_bin) catch {}; + } + + timer.mark(); + var child = std.process.spawn(io, .{ + .argv = &.{tmp_bin}, + }) catch { + std.debug.print("error: failed to run program\n", .{}); + return error.CompileError; + }; + const term = child.wait(io) catch { + std.debug.print("error: program execution failed\n", .{}); + return error.CompileError; + }; + timer.record("exec"); + + switch (term) { + .exited => |code| if (code != 0) std.process.exit(code), + .signal => std.process.exit(1), + .stopped, .unknown => std.process.exit(1), + } +} + +// --- Cache helpers --- + +fn computeCacheKey(source: [:0]const u8, import_sources: *const std.StringHashMap([:0]const u8), target_config: sx.codegen.TargetConfig) u64 { + const Wyhash = std.hash.Wyhash; + var key = Wyhash.hash(0, source); + + // XOR import hashes for order independence (HashMap iteration is non-deterministic) + var import_hash: u64 = 0; + var it = import_sources.iterator(); + while (it.next()) |entry| { + var h = Wyhash.hash(0, entry.key_ptr.*); + h = Wyhash.hash(h, entry.value_ptr.*); + import_hash ^= h; + } + key = Wyhash.hash(key, std.mem.asBytes(&import_hash)); + + // Hash target config fields that affect codegen + if (target_config.triple) |t| key = Wyhash.hash(key, std.mem.span(t)); + if (target_config.cpu) |cp| key = Wyhash.hash(key, std.mem.span(cp)); + if (target_config.features) |f| key = Wyhash.hash(key, std.mem.span(f)); + key = Wyhash.hash(key, std.mem.asBytes(&target_config.opt_level)); + + return key; +} + +fn cachePath(allocator: std.mem.Allocator, key: u64, ext: []const u8) ![:0]const u8 { + return try std.fmt.allocPrintSentinel(allocator, ".sx-cache/{x:0>16}.{s}", .{ key, ext }, 0); +} + +fn saveObjectToCache(obj_buf: sx.llvm_api.c.LLVMMemoryBufferRef, io: std.Io, cache_path: [:0]const u8) void { + const c_api = sx.llvm_api.c; + const start = c_api.LLVMGetBufferStart(obj_buf); + const size = c_api.LLVMGetBufferSize(obj_buf); + if (start == null or size == 0) return; + const data = @as([*]const u8, @ptrCast(start))[0..size]; + // Write to temp file, then copy to cache (make_path creates .sx-cache/ if needed) + std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = ".sx-cache-tmp", .data = data }) catch return; + std.Io.Dir.copyFile(.cwd(), ".sx-cache-tmp", .cwd(), cache_path, io, .{ .make_path = true }) catch {}; + std.Io.Dir.deleteFile(.cwd(), io, ".sx-cache-tmp") catch {}; +} + +fn hasTopLevelRun(root: *const sx.ast.Node) bool { + for (root.data.root.decls) |decl| { + if (decl.data == .comptime_expr) return true; + } + return false; +} + +fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]const []const u8 { + var libs = std.ArrayList([]const u8).empty; + for (root.data.root.decls) |decl| { + switch (decl.data) { + .library_decl => |ld| try libs.append(allocator, ld.lib_name), + .namespace_decl => |ns| { + for (ns.decls) |nd| { + switch (nd.data) { + .library_decl => |ld| try libs.append(allocator, ld.lib_name), + else => {}, + } + } + }, + else => {}, + } + } + return try libs.toOwnedSlice(allocator); +} + +// Simple timing helper — records stage durations and prints a summary table. +const Timing = struct { + const max_entries = 16; + + enabled: bool, + names: [max_entries][]const u8, + durations_ns: [max_entries]u64, + count: usize, + last: ?std.time.Instant, + + fn init(enabled: bool) Timing { + return .{ + .enabled = enabled, + .names = undefined, + .durations_ns = undefined, + .count = 0, + .last = if (enabled) (std.time.Instant.now() catch null) else null, + }; + } + + fn mark(self: *Timing) void { + if (self.enabled) self.last = std.time.Instant.now() catch null; + } + + fn record(self: *Timing, name: []const u8) void { + if (!self.enabled) return; + const now = std.time.Instant.now() catch null; + const elapsed_ns: u64 = if (self.last != null and now != null) now.?.since(self.last.?) else 0; + if (self.count < max_entries) { + self.names[self.count] = name; + self.durations_ns[self.count] = elapsed_ns; + self.count += 1; + } + self.last = now; + } + + fn printAll(self: *const Timing) void { + if (!self.enabled or self.count == 0) return; + var total_ns: u64 = 0; + for (self.durations_ns[0..self.count]) |d| total_ns += d; + + std.debug.print("\n--- timing ---\n", .{}); + for (0..self.count) |idx| { + const ms = @as(f64, @floatFromInt(self.durations_ns[idx])) / 1_000_000.0; + std.debug.print(" {s:<10} {d:>7.1} ms\n", .{ self.names[idx], ms }); + } + const total_ms = @as(f64, @floatFromInt(total_ns)) / 1_000_000.0; + std.debug.print(" {s:<10} {d:>7.1} ms\n", .{ "total", total_ms }); + } +}; diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt index 5230f77..c3308cc 100644 --- a/tests/expected/50-smoke.txt +++ b/tests/expected/50-smoke.txt @@ -13,6 +13,9 @@ heredoc: raw heredoc undef-then-set: 77 enum-lit: .green +null-ptr: null +string-len: 5 +empty-string: 0 === 2. Operators === add: 7 sub: 7 @@ -29,12 +32,19 @@ ge: true chain: true chain-gt: true chain-mixed: true +eq-chain: true +eq-chain-f: false band: 15 bor: 7 +band-var: 15 +bor-var: 7 +mod-var: 2 and: true and-false: false or: true or-false: false +short-and: false +short-or: true ca+=: 15 ca-=: 12 ca*=: 24 @@ -42,6 +52,13 @@ ca/=: 4 prec1: 14 prec2: 20 xx-cast: 200 +widen-u8-s64: 200 +widen-s32-f64: 42.000000 +widen-f32-f64: 1.500000 +widen-u8-s16: 100 +xx-s64-s32: 12345 +xx-f64-f32: 1.500000 +xx-f64-s32: 7 === 3. Types === s8: 127 s16: 32000 @@ -57,14 +74,22 @@ struct-shorthand: Point{x: 5, y: 6} defaults: a=0 b=99 field-assign: Point{x: 42, y: 99} enum: .red +enum-eq: true +enum-neq: true backing: .err tagged: .circle(3.140000) payload: 3.140000 void-variant: .none +reassign: .circle(1.000000) +reassign2: .rect(Shape.rect{w: 5.000000, h: 3.000000}) +enum-prefix: .circle(2.500000) match: rect match-expr: 10 +match-expr-else: 99 capture: 9.500000 +capture-arrow: 7.500000 else-match: other +int-match-else: unknown bool: true union-f: 3.140000 union-i: 1078523331 @@ -72,29 +97,59 @@ promoted-x: 1.000000 promoted-data0: 1.000000 arr[2]: 30 arr.len: 5 +arr-assign: [1, 99, 0] sl[0]: 1 sl.len: 5 +sl-assign: [10, 55, 0] sub: [20, 30, 40] head: [10, 20, 30] tail: [30, 40, 50] +slice-of-slice: [20, 30] strsub: world +str-prefix: hello +str-suffix: world deref: Point{x: 10, y: 20} auto-deref: 10 mp[0]: 10 mp[3]: 40 +mp-write: 99 +vec-construct: [1.000000, 3.000000, 2.000000] +vec-add: [5.000000, 7.000000, 9.000000] +vec-sub: [4.000000, 3.000000, 2.000000] +vec-mul: [2.000000, 6.000000, 12.000000] +vec-div: [5.000000, 3.000000, 2.000000] +vec-scalar: [2.000000, 6.000000, 4.000000] +vec-neg: [-1.000000, -3.000000, -2.000000] +vec-x: 10.000000 +vec-y: 20.000000 +vec-z: 30.000000 +vec-idx: 20.000000 === 4. Control Flow === ite: 1 +ite-both: 10 20 if-block: yes +if-no-else: after +nested-if: deep +if-else-if: second if-block-expr: 15 while: 5 +while-false: skipped while-break: 7 while-continue: 25 +while-sum: 55 +nested-while: 9 +nested-break: 2 2 for: 10 20 30 40 for-print: 10 20 30 40 for-idx: 0 1 2 3 for-2arg: 10@0 20@1 30@2 40@3 for-break: 10 20 for-continue: 10 30 40 +for-slice: 10 20 30 +for-slice-idx: 0:10 1:20 2:30 +for-nested: (0,0) (0,1) (1,0) (1,1) +for-break-idx: 2 +multi: 1 2 3 === 5. Functions === const: 42 typed-const: 3.140000 @@ -105,9 +160,12 @@ early-ret2: 99 void-return: ok generic-s32: 42 generic-f32: 1.500000 +generic-bool: true generic-multi: 30 lambda: 14 lambda-ret: 5.000000 +local-fn: 7 +fn-nested: 26 varargs: 15 spread: 60 fp: 7 @@ -116,33 +174,66 @@ fp-apply: 30 === 6. Scoping === inner: 200 outer: 100 +shadow-type: 42 +shadow-type: 3.140000 nest3: 3 nest2: 2 nest1: 1 +scope-isolate: 100 +scope-reuse: 1 +scope-reuse: 2 +scope-reuse: 1 defer-a defer-b defer-c +d4 +d3 +d2 +d1 inner-defer outer-defer +defer-in-if: body +defer-in-if: deferred === 7. Builtins === write-ok sqrt: 3.000000 +sqrt-f64: 4.000000 sizeof-s32: 4 sizeof-f64: 8 +sizeof-struct: 8 typeof: int +typeof-float: float +typeof-string: string +typeof-bool: bool +typeof-struct: struct +typeof-enum: enum typename: Point fieldcount: 2 +fieldcount-enum: 3 fieldname0: x fieldname1: y +fieldname-enum0: red +fieldname-enum2: blue fieldval0: 11 fieldval1: 22 fieldidx: 1 +fieldidx-tagged: 0 +fieldidx-tagged2: 2 cast: 3 +cast-int-f64: 42.000000 === 8. Comptime === run-const: 25 +run-expr: 42 +run-chain: 30 insert-ok +insert-gen: 42 === 9. Flags === flags: .read | .write has-read: yes +flags-neg: no-read +flags-single: .execute +flags-all: .read | .write | .execute flags-raw: 3 +flags-explicit: .vsync | .resizable +flags-explicit-raw: 68 === DONE ===