perf
This commit is contained in:
@@ -38,6 +38,8 @@ Perms :: enum flags { read; write; execute; }
|
|||||||
|
|
||||||
Status :: enum u8 { ok; err; timeout; }
|
Status :: enum u8 { ok; err; timeout; }
|
||||||
|
|
||||||
|
WindowFlags :: enum flags u32 { vsync :: 64; resizable :: 4; hidden :: 128; }
|
||||||
|
|
||||||
// --- Top-level functions ---
|
// --- Top-level functions ---
|
||||||
|
|
||||||
add :: (a: s32, b: s32) -> s32 { a + b; }
|
add :: (a: s32, b: s32) -> s32 { a + b; }
|
||||||
@@ -72,13 +74,22 @@ early_return :: (x: s32) -> s32 {
|
|||||||
x;
|
x;
|
||||||
}
|
}
|
||||||
|
|
||||||
// #run compile-time constant
|
vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) {
|
||||||
CT_VAL :: #run add(10, 15);
|
.[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 {
|
gen_code :: () -> string {
|
||||||
return "print(\"insert-ok\\n\");";
|
return "print(\"insert-ok\\n\");";
|
||||||
}
|
}
|
||||||
|
gen_val :: () -> string {
|
||||||
|
return "print(\"insert-gen: {}\\n\", 42);";
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
main :: {
|
main :: {
|
||||||
@@ -128,6 +139,18 @@ END;
|
|||||||
c : Color = .green;
|
c : Color = .green;
|
||||||
print("enum-lit: {}\n", c);
|
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
|
// 2. OPERATORS & PRECEDENCE
|
||||||
// ========================================================
|
// ========================================================
|
||||||
@@ -155,16 +178,37 @@ END;
|
|||||||
print("chain-gt: {}\n", 100 > v > 0);
|
print("chain-gt: {}\n", 100 > v > 0);
|
||||||
print("chain-mixed: {}\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
|
// Bitwise
|
||||||
print("band: {}\n", 0xFF & 0x0F);
|
print("band: {}\n", 0xFF & 0x0F);
|
||||||
print("bor: {}\n", 1 | 2 | 4);
|
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)
|
// Logical (short-circuit)
|
||||||
print("and: {}\n", true and true);
|
print("and: {}\n", true and true);
|
||||||
print("and-false: {}\n", true and false);
|
print("and-false: {}\n", true and false);
|
||||||
print("or: {}\n", false or true);
|
print("or: {}\n", false or true);
|
||||||
print("or-false: {}\n", false or false);
|
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
|
// Compound assignment
|
||||||
ca := 10;
|
ca := 10;
|
||||||
ca += 5;
|
ca += 5;
|
||||||
@@ -185,6 +229,36 @@ END;
|
|||||||
small : u8 = xx big2;
|
small : u8 = xx big2;
|
||||||
print("xx-cast: {}\n", small);
|
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
|
// 3. TYPE SYSTEM
|
||||||
// ========================================================
|
// ========================================================
|
||||||
@@ -241,6 +315,13 @@ END;
|
|||||||
ec : Color = .red;
|
ec : Color = .red;
|
||||||
print("enum: {}\n", ec);
|
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
|
// Backing type
|
||||||
st : Status = .err;
|
st : Status = .err;
|
||||||
print("backing: {}\n", st);
|
print("backing: {}\n", st);
|
||||||
@@ -257,6 +338,16 @@ END;
|
|||||||
sh = .none;
|
sh = .none;
|
||||||
print("void-variant: {}\n", sh);
|
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
|
// Pattern matching
|
||||||
sh2 : Shape = .rect(.{ 5, 3 });
|
sh2 : Shape = .rect(.{ 5, 3 });
|
||||||
if sh2 == {
|
if sh2 == {
|
||||||
@@ -274,6 +365,15 @@ END;
|
|||||||
}
|
}
|
||||||
print("match-expr: {}\n", ms);
|
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)
|
// Payload capture (block form)
|
||||||
sh4 : Shape = .circle(9.5);
|
sh4 : Shape = .circle(9.5);
|
||||||
if sh4 == {
|
if sh4 == {
|
||||||
@@ -282,6 +382,14 @@ END;
|
|||||||
case .none: print("capture: none\n");
|
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
|
// else arm in match
|
||||||
num := 42;
|
num := 42;
|
||||||
if num == {
|
if num == {
|
||||||
@@ -298,6 +406,26 @@ END;
|
|||||||
case 3: print("int-match: three\n");
|
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
|
// Bool conditional
|
||||||
flag := true;
|
flag := true;
|
||||||
if flag { print("bool: true\n"); }
|
if flag { print("bool: true\n"); }
|
||||||
@@ -321,11 +449,21 @@ END;
|
|||||||
print("arr[2]: {}\n", arr[2]);
|
print("arr[2]: {}\n", arr[2]);
|
||||||
print("arr.len: {}\n", arr.len);
|
print("arr.len: {}\n", arr.len);
|
||||||
|
|
||||||
|
// Array element assignment
|
||||||
|
aa : [3]s32 = .[1, 2, 3];
|
||||||
|
aa[1] = 99;
|
||||||
|
print("arr-assign: {}\n", aa);
|
||||||
|
|
||||||
// --- Slices ---
|
// --- Slices ---
|
||||||
sl : []s32 = .[1, 2, 3, 4, 5];
|
sl : []s32 = .[1, 2, 3, 4, 5];
|
||||||
print("sl[0]: {}\n", sl[0]);
|
print("sl[0]: {}\n", sl[0]);
|
||||||
print("sl.len: {}\n", sl.len);
|
print("sl.len: {}\n", sl.len);
|
||||||
|
|
||||||
|
// Slice element write
|
||||||
|
sla : []s32 = .[10, 20, 30];
|
||||||
|
sla[1] = 55;
|
||||||
|
print("sl-assign: {}\n", sla);
|
||||||
|
|
||||||
// Subslicing
|
// Subslicing
|
||||||
sub := arr[1..4];
|
sub := arr[1..4];
|
||||||
print("sub: {}\n", sub);
|
print("sub: {}\n", sub);
|
||||||
@@ -334,9 +472,17 @@ END;
|
|||||||
tail := arr[2..];
|
tail := arr[2..];
|
||||||
print("tail: {}\n", tail);
|
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
|
// String subslicing
|
||||||
msg := "hello world";
|
msg := "hello world";
|
||||||
print("strsub: {}\n", msg[6..11]);
|
print("strsub: {}\n", msg[6..11]);
|
||||||
|
print("str-prefix: {}\n", msg[..5]);
|
||||||
|
print("str-suffix: {}\n", msg[6..]);
|
||||||
|
|
||||||
// --- Pointers ---
|
// --- Pointers ---
|
||||||
pv := Point.{ 10, 20 };
|
pv := Point.{ 10, 20 };
|
||||||
@@ -351,6 +497,32 @@ END;
|
|||||||
print("mp[0]: {}\n", mp[0]);
|
print("mp[0]: {}\n", mp[0]);
|
||||||
print("mp[3]: {}\n", mp[3]);
|
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
|
// 4. CONTROL FLOW
|
||||||
// ========================================================
|
// ========================================================
|
||||||
@@ -360,11 +532,38 @@ END;
|
|||||||
ite := if true then 1 else 2;
|
ite := if true then 1 else 2;
|
||||||
print("ite: {}\n", ite);
|
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 block
|
||||||
if 1 < 2 {
|
if 1 < 2 {
|
||||||
print("if-block: yes\n");
|
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
|
// If block as expression
|
||||||
ibe := 10 + if true { 5; } else { 0; };
|
ibe := 10 + if true { 5; } else { 0; };
|
||||||
print("if-block-expr: {}\n", ibe);
|
print("if-block-expr: {}\n", ibe);
|
||||||
@@ -374,6 +573,10 @@ END;
|
|||||||
while wi < 5 { wi += 1; }
|
while wi < 5 { wi += 1; }
|
||||||
print("while: {}\n", wi);
|
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
|
// While with break
|
||||||
wb := 0;
|
wb := 0;
|
||||||
while wb < 100 {
|
while wb < 100 {
|
||||||
@@ -392,7 +595,44 @@ END;
|
|||||||
}
|
}
|
||||||
print("while-continue: {}\n", wsum);
|
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];
|
farr : [4]s32 = .[10, 20, 30, 40];
|
||||||
write("for:");
|
write("for:");
|
||||||
for farr {
|
for farr {
|
||||||
@@ -439,6 +679,44 @@ END;
|
|||||||
}
|
}
|
||||||
write("\n");
|
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
|
// 5. FUNCTIONS & DECLARATIONS
|
||||||
// ========================================================
|
// ========================================================
|
||||||
@@ -470,6 +748,7 @@ END;
|
|||||||
// Generic — single param
|
// Generic — single param
|
||||||
print("generic-s32: {}\n", identity(42));
|
print("generic-s32: {}\n", identity(42));
|
||||||
print("generic-f32: {}\n", identity(1.5));
|
print("generic-f32: {}\n", identity(1.5));
|
||||||
|
print("generic-bool: {}\n", identity(true));
|
||||||
|
|
||||||
// Generic — multiple params
|
// Generic — multiple params
|
||||||
print("generic-multi: {}\n", pair_add(10, 20));
|
print("generic-multi: {}\n", pair_add(10, 20));
|
||||||
@@ -482,6 +761,13 @@ END;
|
|||||||
halve :: (x: f32) -> f32 => x / 2.0;
|
halve :: (x: f32) -> f32 => x / 2.0;
|
||||||
print("lambda-ret: {}\n", halve(10.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)
|
// Variadic (typed)
|
||||||
print("varargs: {}\n", typed_sum(1, 2, 3, 4, 5));
|
print("varargs: {}\n", typed_sum(1, 2, 3, 4, 5));
|
||||||
|
|
||||||
@@ -509,6 +795,14 @@ END;
|
|||||||
}
|
}
|
||||||
print("outer: {}\n", sv);
|
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)
|
// Nested scopes (3 levels)
|
||||||
nv := 1;
|
nv := 1;
|
||||||
{
|
{
|
||||||
@@ -521,6 +815,15 @@ END;
|
|||||||
}
|
}
|
||||||
print("nest1: {}\n", nv);
|
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)
|
// Multiple defers (LIFO order)
|
||||||
{
|
{
|
||||||
defer print("defer-c\n");
|
defer print("defer-c\n");
|
||||||
@@ -528,6 +831,14 @@ END;
|
|||||||
defer print("defer-a\n");
|
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 in nested scopes
|
||||||
{
|
{
|
||||||
defer print("outer-defer\n");
|
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
|
// 7. BUILT-IN FUNCTIONS
|
||||||
// ========================================================
|
// ========================================================
|
||||||
@@ -546,10 +863,12 @@ END;
|
|||||||
|
|
||||||
// sqrt
|
// sqrt
|
||||||
print("sqrt: {}\n", sqrt(9.0));
|
print("sqrt: {}\n", sqrt(9.0));
|
||||||
|
print("sqrt-f64: {}\n", sqrt(16.0));
|
||||||
|
|
||||||
// size_of
|
// size_of
|
||||||
print("sizeof-s32: {}\n", size_of(s32));
|
print("sizeof-s32: {}\n", size_of(s32));
|
||||||
print("sizeof-f64: {}\n", size_of(f64));
|
print("sizeof-f64: {}\n", size_of(f64));
|
||||||
|
print("sizeof-struct: {}\n", size_of(Point));
|
||||||
|
|
||||||
// type_of + category matching
|
// type_of + category matching
|
||||||
tv := 42;
|
tv := 42;
|
||||||
@@ -560,16 +879,58 @@ END;
|
|||||||
else: print("typeof: other\n");
|
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
|
// type_name
|
||||||
print("typename: {}\n", type_name(Point));
|
print("typename: {}\n", type_name(Point));
|
||||||
|
|
||||||
// field_count
|
// field_count on struct
|
||||||
print("fieldcount: {}\n", field_count(Point));
|
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("fieldname0: {}\n", field_name(Point, 0));
|
||||||
print("fieldname1: {}\n", field_name(Point, 1));
|
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)
|
// field_value (use any_to_string to avoid sext-on-Any bug)
|
||||||
fv_pt := Point.{ 11, 22 };
|
fv_pt := Point.{ 11, 22 };
|
||||||
write("fieldval0: ");
|
write("fieldval0: ");
|
||||||
@@ -579,13 +940,21 @@ END;
|
|||||||
write(any_to_string(field_value(fv_pt, 1)));
|
write(any_to_string(field_value(fv_pt, 1)));
|
||||||
write("\n");
|
write("\n");
|
||||||
|
|
||||||
// field_index on enum
|
// field_index on plain enum
|
||||||
fi_c : Color = .green;
|
fi_c : Color = .green;
|
||||||
print("fieldidx: {}\n", field_index(Color, fi_c));
|
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
|
// cast
|
||||||
cval : f64 = 3.7;
|
cval : f64 = 3.7;
|
||||||
print("cast: {}\n", cast(s32) cval);
|
print("cast: {}\n", cast(s32) cval);
|
||||||
|
cv2 : s32 = 42;
|
||||||
|
print("cast-int-f64: {}\n", cast(f64) cv2);
|
||||||
|
|
||||||
// ========================================================
|
// ========================================================
|
||||||
// 8. COMPILE-TIME
|
// 8. COMPILE-TIME
|
||||||
@@ -595,9 +964,18 @@ END;
|
|||||||
// #run constant
|
// #run constant
|
||||||
print("run-const: {}\n", CT_VAL);
|
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 with function
|
||||||
#insert gen_code();
|
#insert gen_code();
|
||||||
|
|
||||||
|
// #insert additional
|
||||||
|
#insert gen_val();
|
||||||
|
|
||||||
// ========================================================
|
// ========================================================
|
||||||
// 9. FLAGS
|
// 9. FLAGS
|
||||||
// ========================================================
|
// ========================================================
|
||||||
@@ -611,8 +989,29 @@ END;
|
|||||||
if perm & .read { print("has-read: yes\n"); }
|
if perm & .read { print("has-read: yes\n"); }
|
||||||
if perm & .execute { print("has-exec: 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
|
// Cast to int
|
||||||
print("flags-raw: {}\n", cast(s64) perm);
|
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");
|
print("=== DONE ===\n");
|
||||||
}
|
}
|
||||||
|
|||||||
197
src/codegen.zig
197
src/codegen.zig
@@ -104,6 +104,12 @@ pub const CodeGen = struct {
|
|||||||
module: c.LLVMModuleRef,
|
module: c.LLVMModuleRef,
|
||||||
builder: c.LLVMBuilderRef,
|
builder: c.LLVMBuilderRef,
|
||||||
allocator: std.mem.Allocator,
|
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
|
// Symbol table: maps variable names to their alloca pointers
|
||||||
named_values: std.StringHashMap(NamedValue),
|
named_values: std.StringHashMap(NamedValue),
|
||||||
@@ -188,6 +194,16 @@ pub const CodeGen = struct {
|
|||||||
function_return_types: std.StringHashMap(Type),
|
function_return_types: std.StringHashMap(Type),
|
||||||
// Target configuration (triple, cpu, opt level, lib paths, linker)
|
// Target configuration (triple, cpu, opt level, lib paths, linker)
|
||||||
target_config: TargetConfig = .{},
|
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 {
|
const DeferredFn = struct {
|
||||||
fd: ast.FnDecl,
|
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 {
|
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 module = c.LLVMModuleCreateWithNameInContext(module_name, ctx);
|
||||||
const builder = c.LLVMCreateBuilderInContext(ctx);
|
const builder = c.LLVMCreateBuilderInContext(ctx);
|
||||||
|
|
||||||
// Initialize LLVM targets and set data layout early so alignment queries work
|
// Initialize LLVM targets — native-only when targeting host, all for cross-compilation
|
||||||
llvm.initAllTargets();
|
if (target_config.triple == null) {
|
||||||
|
llvm.initNativeTarget();
|
||||||
|
} else {
|
||||||
|
llvm.initAllTargets();
|
||||||
|
}
|
||||||
|
|
||||||
const triple_owned = target_config.triple == null;
|
const triple_owned = target_config.triple == null;
|
||||||
const triple = target_config.triple orelse c.LLVMGetDefaultTargetTriple();
|
const triple = target_config.triple orelse c.LLVMGetDefaultTargetTriple();
|
||||||
@@ -326,8 +348,9 @@ pub const CodeGen = struct {
|
|||||||
|
|
||||||
var target: c.LLVMTargetRef = null;
|
var target: c.LLVMTargetRef = null;
|
||||||
var err_msg: [*c]u8 = null;
|
var err_msg: [*c]u8 = null;
|
||||||
|
var tm: c.LLVMTargetMachineRef = null;
|
||||||
if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) == 0) {
|
if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) == 0) {
|
||||||
const tm = c.LLVMCreateTargetMachine(
|
tm = c.LLVMCreateTargetMachine(
|
||||||
target,
|
target,
|
||||||
triple,
|
triple,
|
||||||
target_config.getCpu(),
|
target_config.getCpu(),
|
||||||
@@ -339,7 +362,6 @@ pub const CodeGen = struct {
|
|||||||
const dl = c.LLVMCreateTargetDataLayout(tm);
|
const dl = c.LLVMCreateTargetDataLayout(tm);
|
||||||
c.LLVMSetModuleDataLayout(module, dl);
|
c.LLVMSetModuleDataLayout(module, dl);
|
||||||
c.LLVMDisposeTargetData(dl);
|
c.LLVMDisposeTargetData(dl);
|
||||||
c.LLVMDisposeTargetMachine(tm);
|
|
||||||
} else {
|
} else {
|
||||||
if (err_msg != null) c.LLVMDisposeMessage(err_msg);
|
if (err_msg != null) c.LLVMDisposeMessage(err_msg);
|
||||||
}
|
}
|
||||||
@@ -348,6 +370,8 @@ pub const CodeGen = struct {
|
|||||||
.module = module,
|
.module = module,
|
||||||
.builder = builder,
|
.builder = builder,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
|
.ts_context = ts_ctx,
|
||||||
|
.target_machine = tm,
|
||||||
.named_values = std.StringHashMap(NamedValue).init(allocator),
|
.named_values = std.StringHashMap(NamedValue).init(allocator),
|
||||||
.type_registry = std.StringHashMap(TypeRegistryEntry).init(allocator),
|
.type_registry = std.StringHashMap(TypeRegistryEntry).init(allocator),
|
||||||
.flags_enum_types = std.StringHashMap(void).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),
|
.global_mutable_vars = std.StringHashMap(NamedValue).init(allocator),
|
||||||
.function_return_types = std.StringHashMap(Type).init(allocator),
|
.function_return_types = std.StringHashMap(Type).init(allocator),
|
||||||
.target_config = target_config,
|
.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_libraries.deinit(self.allocator);
|
||||||
self.foreign_fns.deinit();
|
self.foreign_fns.deinit();
|
||||||
c.LLVMDisposeBuilder(self.builder);
|
c.LLVMDisposeBuilder(self.builder);
|
||||||
c.LLVMDisposeModule(self.module);
|
if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm);
|
||||||
c.LLVMContextDispose(self.context);
|
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 {
|
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 {
|
pub fn typeToLLVM(self: *CodeGen, ty: Type) c.LLVMTypeRef {
|
||||||
return switch (ty) {
|
return switch (ty) {
|
||||||
.signed => |w| c.LLVMIntTypeInContext(self.context, w),
|
.signed, .unsigned => |w| switch (w) {
|
||||||
.unsigned => |w| c.LLVMIntTypeInContext(self.context, 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(),
|
.f32 => self.f32Type(),
|
||||||
.f64 => self.f64Type(),
|
.f64 => self.f64Type(),
|
||||||
.void_type => self.voidType(),
|
.void_type => self.voidType(),
|
||||||
@@ -736,15 +782,15 @@ pub const CodeGen = struct {
|
|||||||
return self.buildFatPointer(self.getStringStructType(), ptr, len_val);
|
return self.buildFatPointer(self.getStringStructType(), ptr, len_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLVM type shortcuts
|
// LLVM type shortcuts (cached — no FFI call)
|
||||||
fn i1Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt1TypeInContext(self.context); }
|
fn i1Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i1.?; }
|
||||||
fn i8Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt8TypeInContext(self.context); }
|
fn i8Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i8.?; }
|
||||||
fn i32Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt32TypeInContext(self.context); }
|
fn i32Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i32.?; }
|
||||||
fn i64Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMInt64TypeInContext(self.context); }
|
fn i64Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i64.?; }
|
||||||
fn f32Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMFloatTypeInContext(self.context); }
|
fn f32Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_f32.?; }
|
||||||
fn f64Type(self: *CodeGen) c.LLVMTypeRef { return c.LLVMDoubleTypeInContext(self.context); }
|
fn f64Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_f64.?; }
|
||||||
fn ptrType(self: *CodeGen) c.LLVMTypeRef { return c.LLVMPointerTypeInContext(self.context, 0); }
|
fn ptrType(self: *CodeGen) c.LLVMTypeRef { return self.cached_ptr.?; }
|
||||||
fn voidType(self: *CodeGen) c.LLVMTypeRef { return c.LLVMVoidTypeInContext(self.context); }
|
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 {
|
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 };
|
var indices = [_]c.LLVMValueRef{ self.constInt32(0), idx };
|
||||||
@@ -6206,13 +6252,16 @@ pub const CodeGen = struct {
|
|||||||
// Merge block
|
// Merge block
|
||||||
self.positionAt(merge_bb);
|
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) {
|
if (then_val != null and else_val != null) {
|
||||||
const phi = c.LLVMBuildPhi(self.builder, c.LLVMTypeOf(then_val), "iftmp");
|
const ty = c.LLVMTypeOf(then_val);
|
||||||
var vals = [2]c.LLVMValueRef{ then_val, else_val };
|
if (c.LLVMGetTypeKind(ty) != c.LLVMVoidTypeKind) {
|
||||||
var blocks = [2]c.LLVMBasicBlockRef{ then_bb, else_bb };
|
const phi = c.LLVMBuildPhi(self.builder, ty, "iftmp");
|
||||||
c.LLVMAddIncoming(phi, &vals, &blocks, 2);
|
var vals = [2]c.LLVMValueRef{ then_val, else_val };
|
||||||
return phi;
|
var blocks = [2]c.LLVMBasicBlockRef{ then_bb, else_bb };
|
||||||
|
c.LLVMAddIncoming(phi, &vals, &blocks, 2);
|
||||||
|
return phi;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -7176,39 +7225,12 @@ pub const CodeGen = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn emitToFile(self: *CodeGen, output_path: [*:0]const u8, file_type: c.LLVMCodeGenFileType) !void {
|
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;
|
var err_msg: [*c]u8 = null;
|
||||||
|
if (c.LLVMTargetMachineEmitToFile(tm, self.module, output_path, file_type, &err_msg) != 0) {
|
||||||
if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) != 0) {
|
|
||||||
defer c.LLVMDisposeMessage(err_msg);
|
defer c.LLVMDisposeMessage(err_msg);
|
||||||
const msg = std.mem.span(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});
|
return self.emitErrorFmt("failed to emit file: {s}", .{msg});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7221,6 +7243,75 @@ pub const CodeGen = struct {
|
|||||||
return self.emitToFile(output_path, c.LLVMAssemblyFile);
|
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 {
|
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;
|
var argv = std.ArrayList([]const u8).empty;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
pub const c = @cImport({
|
pub const c = @cImport({
|
||||||
@cInclude("llvm-c/Core.h");
|
@cInclude("llvm-c/Core.h");
|
||||||
@cInclude("llvm-c/Analysis.h");
|
@cInclude("llvm-c/Analysis.h");
|
||||||
@cInclude("llvm-c/BitWriter.h");
|
|
||||||
@cInclude("llvm-c/Target.h");
|
@cInclude("llvm-c/Target.h");
|
||||||
@cInclude("llvm-c/TargetMachine.h");
|
@cInclude("llvm-c/TargetMachine.h");
|
||||||
@cInclude("llvm-c/LLJIT.h");
|
@cInclude("llvm-c/LLJIT.h");
|
||||||
|
|||||||
349
src/main.zig
349
src/main.zig
@@ -23,6 +23,9 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
var input_path: ?[]const u8 = null;
|
var input_path: ?[]const u8 = null;
|
||||||
var target_config = sx.codegen.TargetConfig{};
|
var target_config = sx.codegen.TargetConfig{};
|
||||||
var lib_paths = std.ArrayList([]const u8).empty;
|
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;
|
var i: usize = 2;
|
||||||
while (i < args.len) : (i += 1) {
|
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]});
|
std.debug.print("error: invalid --opt value '{s}' (expected: none/0, less/1, default/2, aggressive/3)\n", .{args[i]});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
explicit_opt = true;
|
||||||
} else if (std.mem.eql(u8, arg, "-o")) {
|
} else if (std.mem.eql(u8, arg, "-o")) {
|
||||||
i += 1;
|
i += 1;
|
||||||
if (i >= args.len) { std.debug.print("error: -o requires a value\n", .{}); return; }
|
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;
|
i += 1;
|
||||||
if (i >= args.len) { std.debug.print("error: --sysroot requires a value\n", .{}); return; }
|
if (i >= args.len) { std.debug.print("error: --sysroot requires a value\n", .{}); return; }
|
||||||
target_config.sysroot = args[i];
|
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")) {
|
} else if (std.mem.startsWith(u8, arg, "-L")) {
|
||||||
if (arg.len > 2) {
|
if (arg.len > 2) {
|
||||||
try lib_paths.append(allocator, arg[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")) {
|
if (std.mem.eql(u8, command, "build")) {
|
||||||
const output_name = target_config.output_path orelse deriveOutputName(path);
|
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});
|
std.debug.print("compiled: {s}\n", .{output_name});
|
||||||
} else if (std.mem.eql(u8, command, "ir")) {
|
} else if (std.mem.eql(u8, command, "ir")) {
|
||||||
emitIR(allocator, io, path, target_config) catch return;
|
emitIR(allocator, io, path, target_config) catch return;
|
||||||
} else if (std.mem.eql(u8, command, "asm")) {
|
} else if (std.mem.eql(u8, command, "asm")) {
|
||||||
emitAsm(allocator, io, path, target_config) catch return;
|
emitAsm(allocator, io, path, target_config) catch return;
|
||||||
} else if (std.mem.eql(u8, command, "run")) {
|
} 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";
|
// Default to -O0 for run (faster compile) unless user explicitly set --opt
|
||||||
compile(allocator, io, path, tmp_bin, target_config) catch return;
|
if (!explicit_opt) target_config.opt_level = .none;
|
||||||
defer {
|
var timer = Timing.init(show_timing);
|
||||||
std.Io.Dir.deleteFile(.cwd(), io, tmp_bin) catch {};
|
|
||||||
}
|
// Phase A: read + parse + resolveImports (for cache key)
|
||||||
var child = std.process.spawn(io, .{
|
timer.mark();
|
||||||
.argv = &.{tmp_bin},
|
const source = readSource(allocator, io, path) catch return;
|
||||||
}) catch {
|
timer.record("read");
|
||||||
std.debug.print("error: failed to run program\n", .{});
|
|
||||||
|
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;
|
return;
|
||||||
};
|
};
|
||||||
const term = child.wait(io) catch {
|
timer.record("jit");
|
||||||
std.debug.print("error: program execution failed\n", .{});
|
timer.printAll();
|
||||||
return;
|
|
||||||
};
|
if (exit_code != 0) std.process.exit(exit_code);
|
||||||
switch (term) {
|
|
||||||
.exited => |code| if (code != 0) std.process.exit(code),
|
|
||||||
.signal => std.process.exit(1),
|
|
||||||
.stopped, .unknown => std.process.exit(1),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
printUsage();
|
printUsage();
|
||||||
}
|
}
|
||||||
@@ -138,6 +197,8 @@ fn printUsage() void {
|
|||||||
\\ -L <path> Library search path (repeatable)
|
\\ -L <path> Library search path (repeatable)
|
||||||
\\ --linker <cmd> Linker command (default: cc)
|
\\ --linker <cmd> Linker command (default: cc)
|
||||||
\\ --sysroot <path> Sysroot for cross-compilation
|
\\ --sysroot <path> 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);
|
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);
|
const source = try readSource(allocator, io, input_path);
|
||||||
|
timer.record("read");
|
||||||
|
|
||||||
var comp = sx.core.Compilation.init(allocator, io, input_path, source, target_config);
|
var comp = sx.core.Compilation.init(allocator, io, input_path, source, target_config);
|
||||||
errdefer comp.deinit();
|
errdefer comp.deinit();
|
||||||
|
|
||||||
|
timer.mark();
|
||||||
comp.parse() catch { comp.renderErrors(); return error.CompileError; };
|
comp.parse() catch { comp.renderErrors(); return error.CompileError; };
|
||||||
comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; };
|
timer.record("parse");
|
||||||
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
|
|
||||||
|
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.?;
|
var cg = &comp.cg.?;
|
||||||
cg.verify() catch { comp.renderErrors(); return error.CompileError; };
|
cg.verify() catch { comp.renderErrors(); return error.CompileError; };
|
||||||
|
timer.record("verify");
|
||||||
|
|
||||||
return comp;
|
return comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
|
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();
|
defer comp.deinit();
|
||||||
comp.cg.?.printIR();
|
comp.cg.?.printIR();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
|
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();
|
defer comp.deinit();
|
||||||
const asm_path = target_config.output_path orelse blk: {
|
const asm_path = target_config.output_path orelse blk: {
|
||||||
const name = deriveOutputName(input_path);
|
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});
|
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 {
|
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 comp = try compilePipeline(allocator, io, input_path, target_config);
|
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();
|
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);
|
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
|
// 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", .{});
|
std.debug.print("error: linking failed\n", .{});
|
||||||
return error.CompileError;
|
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
|
// Clean up object file
|
||||||
std.Io.Dir.deleteFile(.cwd(), io, obj_path) catch {};
|
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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ heredoc: raw heredoc
|
|||||||
|
|
||||||
undef-then-set: 77
|
undef-then-set: 77
|
||||||
enum-lit: .green
|
enum-lit: .green
|
||||||
|
null-ptr: null
|
||||||
|
string-len: 5
|
||||||
|
empty-string: 0
|
||||||
=== 2. Operators ===
|
=== 2. Operators ===
|
||||||
add: 7
|
add: 7
|
||||||
sub: 7
|
sub: 7
|
||||||
@@ -29,12 +32,19 @@ ge: true
|
|||||||
chain: true
|
chain: true
|
||||||
chain-gt: true
|
chain-gt: true
|
||||||
chain-mixed: true
|
chain-mixed: true
|
||||||
|
eq-chain: true
|
||||||
|
eq-chain-f: false
|
||||||
band: 15
|
band: 15
|
||||||
bor: 7
|
bor: 7
|
||||||
|
band-var: 15
|
||||||
|
bor-var: 7
|
||||||
|
mod-var: 2
|
||||||
and: true
|
and: true
|
||||||
and-false: false
|
and-false: false
|
||||||
or: true
|
or: true
|
||||||
or-false: false
|
or-false: false
|
||||||
|
short-and: false
|
||||||
|
short-or: true
|
||||||
ca+=: 15
|
ca+=: 15
|
||||||
ca-=: 12
|
ca-=: 12
|
||||||
ca*=: 24
|
ca*=: 24
|
||||||
@@ -42,6 +52,13 @@ ca/=: 4
|
|||||||
prec1: 14
|
prec1: 14
|
||||||
prec2: 20
|
prec2: 20
|
||||||
xx-cast: 200
|
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 ===
|
=== 3. Types ===
|
||||||
s8: 127
|
s8: 127
|
||||||
s16: 32000
|
s16: 32000
|
||||||
@@ -57,14 +74,22 @@ struct-shorthand: Point{x: 5, y: 6}
|
|||||||
defaults: a=0 b=99
|
defaults: a=0 b=99
|
||||||
field-assign: Point{x: 42, y: 99}
|
field-assign: Point{x: 42, y: 99}
|
||||||
enum: .red
|
enum: .red
|
||||||
|
enum-eq: true
|
||||||
|
enum-neq: true
|
||||||
backing: .err
|
backing: .err
|
||||||
tagged: .circle(3.140000)
|
tagged: .circle(3.140000)
|
||||||
payload: 3.140000
|
payload: 3.140000
|
||||||
void-variant: .none
|
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: rect
|
||||||
match-expr: 10
|
match-expr: 10
|
||||||
|
match-expr-else: 99
|
||||||
capture: 9.500000
|
capture: 9.500000
|
||||||
|
capture-arrow: 7.500000
|
||||||
else-match: other
|
else-match: other
|
||||||
|
int-match-else: unknown
|
||||||
bool: true
|
bool: true
|
||||||
union-f: 3.140000
|
union-f: 3.140000
|
||||||
union-i: 1078523331
|
union-i: 1078523331
|
||||||
@@ -72,29 +97,59 @@ promoted-x: 1.000000
|
|||||||
promoted-data0: 1.000000
|
promoted-data0: 1.000000
|
||||||
arr[2]: 30
|
arr[2]: 30
|
||||||
arr.len: 5
|
arr.len: 5
|
||||||
|
arr-assign: [1, 99, 0]
|
||||||
sl[0]: 1
|
sl[0]: 1
|
||||||
sl.len: 5
|
sl.len: 5
|
||||||
|
sl-assign: [10, 55, 0]
|
||||||
sub: [20, 30, 40]
|
sub: [20, 30, 40]
|
||||||
head: [10, 20, 30]
|
head: [10, 20, 30]
|
||||||
tail: [30, 40, 50]
|
tail: [30, 40, 50]
|
||||||
|
slice-of-slice: [20, 30]
|
||||||
strsub: world
|
strsub: world
|
||||||
|
str-prefix: hello
|
||||||
|
str-suffix: world
|
||||||
deref: Point{x: 10, y: 20}
|
deref: Point{x: 10, y: 20}
|
||||||
auto-deref: 10
|
auto-deref: 10
|
||||||
mp[0]: 10
|
mp[0]: 10
|
||||||
mp[3]: 40
|
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 ===
|
=== 4. Control Flow ===
|
||||||
ite: 1
|
ite: 1
|
||||||
|
ite-both: 10 20
|
||||||
if-block: yes
|
if-block: yes
|
||||||
|
if-no-else: after
|
||||||
|
nested-if: deep
|
||||||
|
if-else-if: second
|
||||||
if-block-expr: 15
|
if-block-expr: 15
|
||||||
while: 5
|
while: 5
|
||||||
|
while-false: skipped
|
||||||
while-break: 7
|
while-break: 7
|
||||||
while-continue: 25
|
while-continue: 25
|
||||||
|
while-sum: 55
|
||||||
|
nested-while: 9
|
||||||
|
nested-break: 2 2
|
||||||
for: 10 20 30 40
|
for: 10 20 30 40
|
||||||
for-print: 10 20 30 40
|
for-print: 10 20 30 40
|
||||||
for-idx: 0 1 2 3
|
for-idx: 0 1 2 3
|
||||||
for-2arg: 10@0 20@1 30@2 40@3
|
for-2arg: 10@0 20@1 30@2 40@3
|
||||||
for-break: 10 20
|
for-break: 10 20
|
||||||
for-continue: 10 30 40
|
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 ===
|
=== 5. Functions ===
|
||||||
const: 42
|
const: 42
|
||||||
typed-const: 3.140000
|
typed-const: 3.140000
|
||||||
@@ -105,9 +160,12 @@ early-ret2: 99
|
|||||||
void-return: ok
|
void-return: ok
|
||||||
generic-s32: 42
|
generic-s32: 42
|
||||||
generic-f32: 1.500000
|
generic-f32: 1.500000
|
||||||
|
generic-bool: true
|
||||||
generic-multi: 30
|
generic-multi: 30
|
||||||
lambda: 14
|
lambda: 14
|
||||||
lambda-ret: 5.000000
|
lambda-ret: 5.000000
|
||||||
|
local-fn: 7
|
||||||
|
fn-nested: 26
|
||||||
varargs: 15
|
varargs: 15
|
||||||
spread: 60
|
spread: 60
|
||||||
fp: 7
|
fp: 7
|
||||||
@@ -116,33 +174,66 @@ fp-apply: 30
|
|||||||
=== 6. Scoping ===
|
=== 6. Scoping ===
|
||||||
inner: 200
|
inner: 200
|
||||||
outer: 100
|
outer: 100
|
||||||
|
shadow-type: 42
|
||||||
|
shadow-type: 3.140000
|
||||||
nest3: 3
|
nest3: 3
|
||||||
nest2: 2
|
nest2: 2
|
||||||
nest1: 1
|
nest1: 1
|
||||||
|
scope-isolate: 100
|
||||||
|
scope-reuse: 1
|
||||||
|
scope-reuse: 2
|
||||||
|
scope-reuse: 1
|
||||||
defer-a
|
defer-a
|
||||||
defer-b
|
defer-b
|
||||||
defer-c
|
defer-c
|
||||||
|
d4
|
||||||
|
d3
|
||||||
|
d2
|
||||||
|
d1
|
||||||
inner-defer
|
inner-defer
|
||||||
outer-defer
|
outer-defer
|
||||||
|
defer-in-if: body
|
||||||
|
defer-in-if: deferred
|
||||||
=== 7. Builtins ===
|
=== 7. Builtins ===
|
||||||
write-ok
|
write-ok
|
||||||
sqrt: 3.000000
|
sqrt: 3.000000
|
||||||
|
sqrt-f64: 4.000000
|
||||||
sizeof-s32: 4
|
sizeof-s32: 4
|
||||||
sizeof-f64: 8
|
sizeof-f64: 8
|
||||||
|
sizeof-struct: 8
|
||||||
typeof: int
|
typeof: int
|
||||||
|
typeof-float: float
|
||||||
|
typeof-string: string
|
||||||
|
typeof-bool: bool
|
||||||
|
typeof-struct: struct
|
||||||
|
typeof-enum: enum
|
||||||
typename: Point
|
typename: Point
|
||||||
fieldcount: 2
|
fieldcount: 2
|
||||||
|
fieldcount-enum: 3
|
||||||
fieldname0: x
|
fieldname0: x
|
||||||
fieldname1: y
|
fieldname1: y
|
||||||
|
fieldname-enum0: red
|
||||||
|
fieldname-enum2: blue
|
||||||
fieldval0: 11
|
fieldval0: 11
|
||||||
fieldval1: 22
|
fieldval1: 22
|
||||||
fieldidx: 1
|
fieldidx: 1
|
||||||
|
fieldidx-tagged: 0
|
||||||
|
fieldidx-tagged2: 2
|
||||||
cast: 3
|
cast: 3
|
||||||
|
cast-int-f64: 42.000000
|
||||||
=== 8. Comptime ===
|
=== 8. Comptime ===
|
||||||
run-const: 25
|
run-const: 25
|
||||||
|
run-expr: 42
|
||||||
|
run-chain: 30
|
||||||
insert-ok
|
insert-ok
|
||||||
|
insert-gen: 42
|
||||||
=== 9. Flags ===
|
=== 9. Flags ===
|
||||||
flags: .read | .write
|
flags: .read | .write
|
||||||
has-read: yes
|
has-read: yes
|
||||||
|
flags-neg: no-read
|
||||||
|
flags-single: .execute
|
||||||
|
flags-all: .read | .write | .execute
|
||||||
flags-raw: 3
|
flags-raw: 3
|
||||||
|
flags-explicit: .vsync | .resizable
|
||||||
|
flags-explicit-raw: 68
|
||||||
=== DONE ===
|
=== DONE ===
|
||||||
|
|||||||
Reference in New Issue
Block a user