fixes
This commit is contained in:
31
examples/issue-0002.sx
Normal file
31
examples/issue-0002.sx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// issue-0002: impl for built-in types fails with "expected type name after 'for'"
|
||||||
|
//
|
||||||
|
// `impl Protocol for f32` should work the same as `impl Protocol for MyStruct`.
|
||||||
|
// Currently the parser rejects built-in type names (f32, s64, bool, etc.) after `for`.
|
||||||
|
|
||||||
|
Lerpable :: protocol #inline {
|
||||||
|
lerp :: (b: Self, t: f32) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lerpable for f32 {
|
||||||
|
lerp :: (self: f32, b: f32, t: f32) -> f32 { self + (b - self) * t; }
|
||||||
|
}
|
||||||
|
|
||||||
|
do_lerp :: (a: Lerpable, b: f32, t: f32) -> f32 {
|
||||||
|
a.lerp(b, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
// Static call through impl
|
||||||
|
result := f32.lerp(0.0, 10.0, 0.5);
|
||||||
|
print("lerp(0, 10, 0.5) = {}\n", result);
|
||||||
|
|
||||||
|
// Protocol dispatch through #inline erasure
|
||||||
|
val : f32 = 0.0;
|
||||||
|
p : *f32 = @val;
|
||||||
|
l : Lerpable = xx p;
|
||||||
|
result2 := do_lerp(l, 10.0, 0.25);
|
||||||
|
print("lerp(0, 10, 0.25) = {}\n", result2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
83
examples/issue-0003.sx
Normal file
83
examples/issue-0003.sx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// issue-0003: Generic struct with protocol #inline constraint generates wrong LLVM types
|
||||||
|
//
|
||||||
|
// When `Animated($T: Lerpable)` is monomorphized with a struct type like `Size`,
|
||||||
|
// the LLVM IR generates `{}` (empty type) instead of the actual struct layout
|
||||||
|
// for the `T` parameter in methods like `set_immediate`, `animate_to`, and `lerp`.
|
||||||
|
//
|
||||||
|
// Error: "Call parameter type does not match function signature!"
|
||||||
|
// call void @Animated__0.set_immediate(ptr ..., { float, float } ...)
|
||||||
|
// expected {} but got { float, float }
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "modules/math";
|
||||||
|
|
||||||
|
Lerpable :: protocol #inline {
|
||||||
|
lerp :: (b: Self, t: f32) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size :: struct {
|
||||||
|
width, height: f32;
|
||||||
|
zero :: () -> Size => .{ width = 0.0, height = 0.0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lerpable for Size {
|
||||||
|
lerp :: (self: Size, b: Size, t: f32) -> Size {
|
||||||
|
Size.{ width = self.width + (b.width - self.width) * t,
|
||||||
|
height = self.height + (b.height - self.height) * t };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Animated :: struct ($T: Lerpable) {
|
||||||
|
current: T;
|
||||||
|
from: T;
|
||||||
|
to: T;
|
||||||
|
elapsed: f32;
|
||||||
|
duration: f32;
|
||||||
|
active: bool;
|
||||||
|
|
||||||
|
make :: (value: T) -> Animated(T) {
|
||||||
|
Animated(T).{
|
||||||
|
current = value, from = value, to = value,
|
||||||
|
elapsed = 0.0, duration = 0.0, active = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
set_immediate :: (self: *Animated(T), value: T) {
|
||||||
|
self.current = value;
|
||||||
|
self.from = value;
|
||||||
|
self.to = value;
|
||||||
|
self.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
animate_to :: (self: *Animated(T), target: T, dur: f32) {
|
||||||
|
self.from = self.current;
|
||||||
|
self.to = target;
|
||||||
|
self.elapsed = 0.0;
|
||||||
|
self.duration = dur;
|
||||||
|
self.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tick :: (self: *Animated(T), dt: f32) {
|
||||||
|
if !self.active { return; }
|
||||||
|
self.elapsed += dt;
|
||||||
|
t := clamp(self.elapsed / self.duration, 0.0, 1.0);
|
||||||
|
self.current = self.from.lerp(self.to, t);
|
||||||
|
if t >= 1.0 {
|
||||||
|
self.current = self.to;
|
||||||
|
self.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
anim := Animated(Size).make(Size.zero());
|
||||||
|
anim.set_immediate(Size.{ width = 100.0, height = 50.0 });
|
||||||
|
print("after set: {}x{}\n", anim.current.width, anim.current.height);
|
||||||
|
|
||||||
|
anim.animate_to(Size.{ width = 200.0, height = 100.0 }, 1.0);
|
||||||
|
anim.tick(0.5);
|
||||||
|
print("mid anim: {}x{}\n", anim.current.width, anim.current.height);
|
||||||
|
|
||||||
|
anim.tick(0.5);
|
||||||
|
print("end anim: {}x{}\n", anim.current.width, anim.current.height);
|
||||||
|
}
|
||||||
43
examples/issue-0004.sx
Normal file
43
examples/issue-0004.sx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// issue-0004: scalar-to-vector conversion when all optional struct fields are null
|
||||||
|
//
|
||||||
|
// When a struct has multiple ?f32 fields and ALL are set to null simultaneously,
|
||||||
|
// passing that struct to a virtual function call triggers:
|
||||||
|
// "error: scalar-to-vector conversion failed"
|
||||||
|
//
|
||||||
|
// Setting at least one field to a concrete value works fine.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
ProposedSize :: struct {
|
||||||
|
width: ?f32;
|
||||||
|
height: ?f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sizable :: protocol {
|
||||||
|
size :: (proposal: ProposedSize) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget :: struct {}
|
||||||
|
|
||||||
|
impl Sizable for Widget {
|
||||||
|
size :: (self: *Widget, proposal: ProposedSize) -> f32 {
|
||||||
|
w := if pw := proposal.width { pw; } else { 100.0; };
|
||||||
|
h := if ph := proposal.height { ph; } else { 100.0; };
|
||||||
|
w + h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
w := Widget.{};
|
||||||
|
s : Sizable = w;
|
||||||
|
|
||||||
|
// These work:
|
||||||
|
r1 := s.size(ProposedSize.{ width = 50.0, height = null });
|
||||||
|
print("r1 = {}\n", r1);
|
||||||
|
r2 := s.size(ProposedSize.{ width = null, height = 50.0 });
|
||||||
|
print("r2 = {}\n", r2);
|
||||||
|
|
||||||
|
// This fails with "scalar-to-vector conversion failed":
|
||||||
|
r3 := s.size(ProposedSize.{ width = null, height = null });
|
||||||
|
print("r3 = {}\n", r3);
|
||||||
|
}
|
||||||
59
examples/issue-0005.sx
Normal file
59
examples/issue-0005.sx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// issue-0005: optional f32 field in struct loses value when earlier optional is null
|
||||||
|
//
|
||||||
|
// FIXED: null_literal was double-wrapped as Some in struct literal coercion.
|
||||||
|
// Root cause: inferExprType(null) returns .void, coerceToType(.void, ?f32)
|
||||||
|
// tried to wrap the already-null value as Some, corrupting the struct.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
ProposedSize :: struct {
|
||||||
|
width: ?f32;
|
||||||
|
height: ?f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct function — does it work?
|
||||||
|
direct_size :: (proposal: ProposedSize) -> f32 {
|
||||||
|
w := if pw := proposal.width { pw; } else { 100.0; };
|
||||||
|
h := if ph := proposal.height { ph; } else { 100.0; };
|
||||||
|
w + h;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sizable :: protocol {
|
||||||
|
size :: (proposal: ProposedSize) -> f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget :: struct {}
|
||||||
|
|
||||||
|
impl Sizable for Widget {
|
||||||
|
size :: (self: *Widget, proposal: ProposedSize) -> f32 {
|
||||||
|
w := if pw := proposal.width { pw; } else { 100.0; };
|
||||||
|
h := if ph := proposal.height { ph; } else { 100.0; };
|
||||||
|
w + h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
// Test 1: Direct call
|
||||||
|
print("=== Direct calls ===\n");
|
||||||
|
d1 := direct_size(ProposedSize.{ width = 50.0, height = null });
|
||||||
|
print("d1 = {}\n", d1);
|
||||||
|
d2 := direct_size(ProposedSize.{ width = null, height = 50.0 });
|
||||||
|
print("d2 = {}\n", d2);
|
||||||
|
d3 := direct_size(ProposedSize.{ width = null, height = null });
|
||||||
|
print("d3 = {}\n", d3);
|
||||||
|
d4 := direct_size(ProposedSize.{ width = 50.0, height = 60.0 });
|
||||||
|
print("d4 = {}\n", d4);
|
||||||
|
|
||||||
|
// Test 2: Protocol dispatch
|
||||||
|
print("=== Protocol dispatch ===\n");
|
||||||
|
w := Widget.{};
|
||||||
|
s : Sizable = w;
|
||||||
|
r1 := s.size(ProposedSize.{ width = 50.0, height = null });
|
||||||
|
print("r1 = {}\n", r1);
|
||||||
|
r2 := s.size(ProposedSize.{ width = null, height = 50.0 });
|
||||||
|
print("r2 = {}\n", r2);
|
||||||
|
r3 := s.size(ProposedSize.{ width = null, height = null });
|
||||||
|
print("r3 = {}\n", r3);
|
||||||
|
r4 := s.size(ProposedSize.{ width = 50.0, height = 60.0 });
|
||||||
|
print("r4 = {}\n", r4);
|
||||||
|
}
|
||||||
21
examples/issue-0006.sx
Normal file
21
examples/issue-0006.sx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// issue-0006: literal `0` in integer comparison inferred as float inside f32 ternary
|
||||||
|
//
|
||||||
|
// When `s64 != 0` is used as the condition of a ternary whose result type is f32,
|
||||||
|
// the literal `0` in the comparison leaks the ternary's f32 type instead of matching
|
||||||
|
// the LHS s64 type. This generates invalid LLVM IR:
|
||||||
|
// %icmp = icmp ne i64 %load, float 0.000000e+00
|
||||||
|
//
|
||||||
|
// The same comparison works fine in a regular if-statement.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
x : s64 = 42;
|
||||||
|
|
||||||
|
// OK: comparison in statement context
|
||||||
|
if x != 0 { out("ok\n"); }
|
||||||
|
|
||||||
|
// BUG: comparison as condition of f32 ternary — `0` inferred as f32
|
||||||
|
result : f32 = if x != 0 then 1.0 else 2.0;
|
||||||
|
print("result = {}\n", result);
|
||||||
|
}
|
||||||
55
examples/issue-0007.sx
Normal file
55
examples/issue-0007.sx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// issue-0007: protocol value stores dangling pointer to stack local
|
||||||
|
//
|
||||||
|
// When a concrete value is converted to a protocol value inside a function,
|
||||||
|
// and the protocol value is stored in a List (via a wrapper struct), the
|
||||||
|
// protocol value's data pointer points to the stack-local variable rather
|
||||||
|
// than a heap-allocated copy. After the function returns, the pointer is
|
||||||
|
// dangling and method dispatch crashes (SIGSEGV/SIGBUS).
|
||||||
|
//
|
||||||
|
// Inside the function: dispatch works (stack local still alive)
|
||||||
|
// After the function returns: dispatch crashes (stack local gone)
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Sizable :: protocol {
|
||||||
|
size :: () -> s64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget :: struct { value: s64; }
|
||||||
|
impl Sizable for Widget {
|
||||||
|
size :: (self: *Widget) -> s64 { self.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper struct with a protocol field (like ViewChild)
|
||||||
|
Item :: struct {
|
||||||
|
view: Sizable;
|
||||||
|
}
|
||||||
|
|
||||||
|
Container :: struct {
|
||||||
|
items: List(Item);
|
||||||
|
|
||||||
|
add :: (self: *Container, w: Widget) {
|
||||||
|
p := w; // local copy
|
||||||
|
self.items.append(Item.{ view = p }); // protocol created from stack local `p`
|
||||||
|
|
||||||
|
// Works here: stack local `p` is still alive
|
||||||
|
out("inside add: ");
|
||||||
|
print("{}\n", self.items.items[self.items.len - 1].view.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
c : Container = .{};
|
||||||
|
c.add(Widget.{ value = 42 });
|
||||||
|
c.add(Widget.{ value = 99 });
|
||||||
|
|
||||||
|
// BUG: items[0] should return 42, but returns 99 (reads items[1]'s stack slot)
|
||||||
|
// Both protocol values point to the same stack address (the `p` local in add())
|
||||||
|
r0 := c.items.items[0].view.size();
|
||||||
|
r1 := c.items.items[1].view.size();
|
||||||
|
print("items[0] = {} (expected 42)\n", r0);
|
||||||
|
print("items[1] = {} (expected 99)\n", r1);
|
||||||
|
|
||||||
|
// With more stack activity between add() and the reads, this crashes
|
||||||
|
// (stack memory overwritten by other function calls)
|
||||||
|
}
|
||||||
42
examples/issue-0008-bare.sx
Normal file
42
examples/issue-0008-bare.sx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Minimal: protocol dispatch on List(Protocol) items from a function
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Sizable :: protocol {
|
||||||
|
size :: () -> s64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Leaf :: struct { value: s64; }
|
||||||
|
impl Sizable for Leaf {
|
||||||
|
size :: (self: *Leaf) -> s64 { self.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
add :: (items: *List(Sizable), w: Leaf) {
|
||||||
|
p := w;
|
||||||
|
items.append(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_fn :: (items: *List(Sizable)) {
|
||||||
|
out("dispatch_fn: about to dispatch\n");
|
||||||
|
s := items.items[0].size();
|
||||||
|
print("dispatch_fn: result = {}\n", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
items : List(Sizable) = .{};
|
||||||
|
add(@items, Leaf.{ value = 42 });
|
||||||
|
|
||||||
|
// Direct dispatch twice
|
||||||
|
out("=== Direct 1 ===\n");
|
||||||
|
r1 := items.items[0].size();
|
||||||
|
print("r1 = {}\n", r1);
|
||||||
|
|
||||||
|
out("=== Direct 2 ===\n");
|
||||||
|
r2 := items.items[0].size();
|
||||||
|
print("r2 = {}\n", r2);
|
||||||
|
|
||||||
|
// Then from function
|
||||||
|
out("=== From function ===\n");
|
||||||
|
dispatch_fn(@items);
|
||||||
|
out("=== OK ===\n");
|
||||||
|
}
|
||||||
52
examples/issue-0008.sx
Normal file
52
examples/issue-0008.sx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// issue-0008: protocol value created in a function and appended to a list
|
||||||
|
// still stores a dangling stack pointer (issue-0007 fix incomplete)
|
||||||
|
//
|
||||||
|
// When a concrete value is converted to a protocol value inside a function
|
||||||
|
// (either implicitly via append or explicitly) and stored in a List, the
|
||||||
|
// protocol data pointer targets the function's stack frame instead of a
|
||||||
|
// heap-allocated copy.
|
||||||
|
//
|
||||||
|
// After the function returns, the first dispatch may succeed (stack not yet
|
||||||
|
// overwritten), but subsequent dispatches crash because the stack memory has
|
||||||
|
// been reused by other calls.
|
||||||
|
//
|
||||||
|
// STATUS: open — issue-0007 fix only covers some cases
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Sizable :: protocol {
|
||||||
|
size :: () -> s64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Leaf :: struct { value: s64; }
|
||||||
|
impl Sizable for Leaf {
|
||||||
|
size :: (self: *Leaf) -> s64 { self.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
add :: (items: *List(Sizable), w: Leaf) {
|
||||||
|
p := w;
|
||||||
|
items.append(p); // protocol value created from stack local `p`
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
// Works: protocol value created in main, appended to list
|
||||||
|
out("=== Created in main ===\n");
|
||||||
|
list_a : List(Sizable) = .{};
|
||||||
|
s : Sizable = Leaf.{ value = 42 };
|
||||||
|
list_a.append(s);
|
||||||
|
r1 := list_a.items[0].size();
|
||||||
|
print("first: {} (expected 42)\n", r1);
|
||||||
|
r2 := list_a.items[0].size();
|
||||||
|
print("second: {} (expected 42)\n", r2);
|
||||||
|
|
||||||
|
// BUG: protocol value created in add(), first dispatch works, second crashes
|
||||||
|
out("=== Created in add() ===\n");
|
||||||
|
list_b : List(Sizable) = .{};
|
||||||
|
add(@list_b, Leaf.{ value = 99 });
|
||||||
|
r3 := list_b.items[0].size();
|
||||||
|
print("first: {} (expected 99)\n", r3); // works (stack not yet clobbered)
|
||||||
|
r4 := list_b.items[0].size();
|
||||||
|
print("second: {} (expected 99)\n", r4); // CRASH: stack memory reused
|
||||||
|
|
||||||
|
out("=== OK ===\n");
|
||||||
|
}
|
||||||
23
examples/issue-0009.sx
Normal file
23
examples/issue-0009.sx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// issue-0009: `push` with Context containing inline protocol triggers LLVM verification error
|
||||||
|
//
|
||||||
|
// LLVM verification failed: Invalid InsertValueInst operands!
|
||||||
|
// %si24 = insertvalue { ptr, ptr, ptr } undef, { ptr, ptr, ptr } %si21, 0
|
||||||
|
//
|
||||||
|
// Context contains `allocator: Allocator` where `Allocator :: protocol #inline`.
|
||||||
|
// push saves/restores context as a value, but LLVM lowering mishandles the struct
|
||||||
|
// when the first field is an inline protocol cast from a different impl (Arena).
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "modules/allocators.sx";
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
arena : Arena = ---;
|
||||||
|
arena.create(context.allocator, 4096);
|
||||||
|
|
||||||
|
new_ctx := Context.{ allocator = xx @arena, data = context.data };
|
||||||
|
push new_ctx {
|
||||||
|
ptr := context.allocator.alloc(128);
|
||||||
|
out("inside push\n");
|
||||||
|
}
|
||||||
|
out("after push\n");
|
||||||
|
}
|
||||||
34
examples/issue-0010.sx
Normal file
34
examples/issue-0010.sx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// issue-0010: Closure returning a protocol value generates invalid LLVM IR
|
||||||
|
//
|
||||||
|
// LLVM verification failed: Called function must be a pointer!
|
||||||
|
// %icall = call addrspace(64) i64 %load56()
|
||||||
|
//
|
||||||
|
// A Closure() -> MyProtocol where MyProtocol is a protocol (not #inline)
|
||||||
|
// fails at codegen. Calling the function directly works fine; only the
|
||||||
|
// closure dispatch path is broken.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
MyProtocol :: protocol {
|
||||||
|
get_value :: () -> s64;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyImpl :: struct { value: s64; }
|
||||||
|
impl MyProtocol for MyImpl {
|
||||||
|
get_value :: (self: *MyImpl) -> s64 { self.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
make_thing :: () -> MyProtocol {
|
||||||
|
MyImpl.{ value = 42 };
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
// Direct call works:
|
||||||
|
v := make_thing();
|
||||||
|
out("direct call works\n");
|
||||||
|
|
||||||
|
// Closure call crashes:
|
||||||
|
c := closure(make_thing);
|
||||||
|
result := c();
|
||||||
|
out("closure call works\n");
|
||||||
|
}
|
||||||
51
examples/issue-0011.sx
Normal file
51
examples/issue-0011.sx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// issue-0011: Assigning to List(T).items corrupts adjacent memory when T is large
|
||||||
|
//
|
||||||
|
// Writing `list.items = xx 0` overwrites memory beyond the 8-byte items pointer
|
||||||
|
// when the List's element type T is larger than 32 bytes. The corruption spills
|
||||||
|
// into the struct field that follows the List in memory.
|
||||||
|
//
|
||||||
|
// Works correctly when size_of(T) <= 32.
|
||||||
|
// Fails when size_of(T) > 32 (e.g., 40 bytes).
|
||||||
|
//
|
||||||
|
// Likely cause: codegen confuses size_of(T) with size_of([*]T) when generating
|
||||||
|
// the store instruction for the items field.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
// 40-byte struct — triggers the bug.
|
||||||
|
// Shrink to [4]s64 (32 bytes) and the bug goes away.
|
||||||
|
BigNode :: struct {
|
||||||
|
data: [5]s64; // 40 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree :: struct {
|
||||||
|
nodes: List(BigNode); // items(8) + len(8) + cap(8) = 24 bytes
|
||||||
|
generation: s64; // 8 bytes — total 32 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
Container :: struct {
|
||||||
|
tree: Tree;
|
||||||
|
sentinel: s64;
|
||||||
|
|
||||||
|
do_work :: (self: *Container) {
|
||||||
|
self.tree.nodes.items = xx 0; // BUG: corrupts self.sentinel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
obj : *Container = xx context.allocator.alloc(size_of(Container));
|
||||||
|
memset(obj, 0, size_of(Container));
|
||||||
|
obj.sentinel = 0xDEADBEEF;
|
||||||
|
|
||||||
|
print("size_of BigNode = {}\n", size_of(BigNode));
|
||||||
|
print("before: sentinel = {}\n", obj.sentinel);
|
||||||
|
|
||||||
|
obj.do_work();
|
||||||
|
|
||||||
|
print("after: sentinel = {}\n", obj.sentinel);
|
||||||
|
if obj.sentinel != 0xDEADBEEF {
|
||||||
|
print("BUG: sentinel was corrupted!\n");
|
||||||
|
} else {
|
||||||
|
out("OK\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
87
examples/issue-0012.sx
Normal file
87
examples/issue-0012.sx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// issue-0012: Pattern match expression with mixed null/concrete arms
|
||||||
|
//
|
||||||
|
// A match expression with both `null` arms and concrete struct value arms
|
||||||
|
// should produce an optional type (?T) and correctly wrap non-null values.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
HAlignment :: enum { leading; center; trailing; }
|
||||||
|
VAlignment :: enum { top; center; bottom; }
|
||||||
|
Alignment :: struct { h: HAlignment; v: VAlignment; }
|
||||||
|
|
||||||
|
ALIGN_CENTER :: Alignment.{ h = .center, v = .center };
|
||||||
|
ALIGN_TOP :: Alignment.{ h = .center, v = .top };
|
||||||
|
ALIGN_BOTTOM :: Alignment.{ h = .center, v = .bottom };
|
||||||
|
ALIGN_LEADING :: Alignment.{ h = .leading, v = .center };
|
||||||
|
ALIGN_TRAILING :: Alignment.{ h = .trailing, v = .center };
|
||||||
|
ALIGN_TOP_LEADING :: Alignment.{ h = .leading, v = .top };
|
||||||
|
ALIGN_TOP_TRAILING :: Alignment.{ h = .trailing, v = .top };
|
||||||
|
ALIGN_BOTTOM_LEADING :: Alignment.{ h = .leading, v = .bottom };
|
||||||
|
ALIGN_BOTTOM_TRAILING :: Alignment.{ h = .trailing, v = .bottom };
|
||||||
|
|
||||||
|
Zone :: enum {
|
||||||
|
floating;
|
||||||
|
fill;
|
||||||
|
center;
|
||||||
|
top;
|
||||||
|
bottom;
|
||||||
|
left;
|
||||||
|
right;
|
||||||
|
top_left;
|
||||||
|
top_right;
|
||||||
|
bottom_left;
|
||||||
|
bottom_right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match expression as implicit return with mixed null/concrete arms
|
||||||
|
zone_to_alignment :: (zone: Zone) -> ?Alignment {
|
||||||
|
if zone == {
|
||||||
|
case .floating: null;
|
||||||
|
case .fill: ALIGN_CENTER;
|
||||||
|
case .center: ALIGN_CENTER;
|
||||||
|
case .top: ALIGN_TOP;
|
||||||
|
case .bottom: ALIGN_BOTTOM;
|
||||||
|
case .left: ALIGN_LEADING;
|
||||||
|
case .right: ALIGN_TRAILING;
|
||||||
|
case .top_left: ALIGN_TOP_LEADING;
|
||||||
|
case .top_right: ALIGN_TOP_TRAILING;
|
||||||
|
case .bottom_left: ALIGN_BOTTOM_LEADING;
|
||||||
|
case .bottom_right: ALIGN_BOTTOM_TRAILING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Side-effect match inside a function returning bool — must NOT be
|
||||||
|
// affected by optional inference (no null arms here)
|
||||||
|
NodeType :: enum { rect; text; image; }
|
||||||
|
process_node :: (t: NodeType) -> bool {
|
||||||
|
if t == {
|
||||||
|
case .rect: { out("rect\n"); }
|
||||||
|
case .text: { out("text\n"); }
|
||||||
|
case .image: { out("image\n"); }
|
||||||
|
}
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
// Test null arm
|
||||||
|
r0 := zone_to_alignment(.floating);
|
||||||
|
if a := r0 { print("BUG: floating should be null, got h={}\n", xx a.h); }
|
||||||
|
else { out("ok: floating is null\n"); }
|
||||||
|
|
||||||
|
// Test concrete arms
|
||||||
|
r1 := zone_to_alignment(.left);
|
||||||
|
if a := r1 { print("ok: left h={}\n", xx a.h); }
|
||||||
|
else { out("BUG: left returned null\n"); }
|
||||||
|
|
||||||
|
r2 := zone_to_alignment(.center);
|
||||||
|
if a := r2 { print("ok: center h={}\n", xx a.h); }
|
||||||
|
else { out("BUG: center returned null\n"); }
|
||||||
|
|
||||||
|
r3 := zone_to_alignment(.top_right);
|
||||||
|
if a := r3 { print("ok: top_right h={} v={}\n", xx a.h, xx a.v); }
|
||||||
|
else { out("BUG: top_right returned null\n"); }
|
||||||
|
|
||||||
|
// Test side-effect match (no null arms) still works
|
||||||
|
process_node(.rect);
|
||||||
|
process_node(.text);
|
||||||
|
}
|
||||||
36
examples/issue-0013.sx
Normal file
36
examples/issue-0013.sx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// issue-0013: += on global variables reads initial value instead of current value
|
||||||
|
//
|
||||||
|
// `g_counter += 1` compiles as `store(initial_value + 1)` instead of
|
||||||
|
// `store(load(g_counter) + 1)`. So it always produces the same result.
|
||||||
|
//
|
||||||
|
// Workaround: use `g_counter = g_counter + 1` instead of `g_counter += 1`
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
g_counter : s64 = 0;
|
||||||
|
|
||||||
|
tick :: () {
|
||||||
|
g_counter += 1;
|
||||||
|
print("counter={}\n", g_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
// Test 1: += always produces 1 (BUG)
|
||||||
|
out("--- Test 1: += (broken) ---\n");
|
||||||
|
out("Expected: 1, 2, 3\n");
|
||||||
|
i : s64 = 0;
|
||||||
|
while i < 3 {
|
||||||
|
tick();
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: manual read-modify-write works correctly
|
||||||
|
out("--- Test 2: = x + 1 (works) ---\n");
|
||||||
|
out("Expected: 2, 3, 4\n");
|
||||||
|
g_counter = g_counter + 1;
|
||||||
|
print("counter={}\n", g_counter);
|
||||||
|
g_counter = g_counter + 1;
|
||||||
|
print("counter={}\n", g_counter);
|
||||||
|
g_counter = g_counter + 1;
|
||||||
|
print("counter={}\n", g_counter);
|
||||||
|
}
|
||||||
15
examples/issue-0014.sx
Normal file
15
examples/issue-0014.sx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// issue-0014: Feature request — {{{ CONTENT_HASH }}} template variable for wasm shell
|
||||||
|
//
|
||||||
|
// When targeting wasm, the compiler processes shell.html and substitutes
|
||||||
|
// {{{ SCRIPT }}} with the <script> tag. Add a {{{ CONTENT_HASH }}} variable
|
||||||
|
// that is a short hash (e.g. 8-char hex) of the final build outputs
|
||||||
|
// (.js + .wasm + .data), so the shell can use it for cache busting:
|
||||||
|
//
|
||||||
|
// <script>
|
||||||
|
// Module.locateFile=function(path){return path+'?v={{{ CONTENT_HASH }}}'};
|
||||||
|
// </script>
|
||||||
|
// <script async src="index.js?v={{{ CONTENT_HASH }}}"></script>
|
||||||
|
//
|
||||||
|
// This lets browsers cache until the next build, then bust automatically.
|
||||||
|
// No changes needed to build.sx or modules/compiler.sx — just the compiler
|
||||||
|
// recognizing the new placeholder during shell template substitution.
|
||||||
28
examples/issue-0015.sx
Normal file
28
examples/issue-0015.sx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// issue-0015: Global array variables with initializers contain all zeros at runtime.
|
||||||
|
//
|
||||||
|
// Expected: VALS[0]=-2, VALS[1]=-1, VALS[2]=42
|
||||||
|
// Actual: VALS[0]=0, VALS[1]=0, VALS[2]=0
|
||||||
|
//
|
||||||
|
// Global arrays declared with `: [N]T = .[...]` syntax get zero-initialized
|
||||||
|
// instead of receiving their specified values.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
VALS : [4]s32 = .[-2, -1, 42, 99];
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
out("VALS: ");
|
||||||
|
i := 0;
|
||||||
|
while i < 4 {
|
||||||
|
out(int_to_string(xx VALS[i]));
|
||||||
|
out(" ");
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
out("\n");
|
||||||
|
|
||||||
|
if VALS[0] == -2 and VALS[1] == -1 and VALS[2] == 42 and VALS[3] == 99 {
|
||||||
|
out("PASS\n");
|
||||||
|
} else {
|
||||||
|
out("FAIL: global array not initialized\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
examples/issue-0016.sx
Normal file
17
examples/issue-0016.sx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// issue-0016: C calling convention for function pointers passed to foreign callbacks.
|
||||||
|
//
|
||||||
|
// `callconv(.c)` ensures the function uses C ABI, so it can be safely
|
||||||
|
// passed as a callback to #foreign functions like SDL_AddEventWatch.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
// A function with C calling convention
|
||||||
|
add_c :: (a: s64, b: s64) -> s64 callconv(.c) {
|
||||||
|
a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
// Call it directly — should work like any other function
|
||||||
|
result := add_c(10, 32);
|
||||||
|
print("callconv(.c): {}\n", result);
|
||||||
|
}
|
||||||
53
examples/issue-0017.sx
Normal file
53
examples/issue-0017.sx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// issue-0017: Investigate data corruption in callconv(.c) callbacks
|
||||||
|
// when accessing struct methods on global pointers.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Pipe :: struct {
|
||||||
|
pw: s32;
|
||||||
|
ph: s32;
|
||||||
|
frame: s32;
|
||||||
|
|
||||||
|
resize :: (self: *Pipe, nw: s32, nh: s32) {
|
||||||
|
self.pw = nw;
|
||||||
|
self.ph = nh;
|
||||||
|
}
|
||||||
|
|
||||||
|
tick :: (self: *Pipe) {
|
||||||
|
self.frame = self.frame + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_pipe : *Pipe = ---;
|
||||||
|
g_width : s32 = 800;
|
||||||
|
g_height : s32 = 600;
|
||||||
|
|
||||||
|
do_render :: () {
|
||||||
|
g_pipe.resize(g_width, g_height);
|
||||||
|
g_pipe.tick();
|
||||||
|
print("wrapper: pw={}, ph={}, frame={}\n", g_pipe.pw, g_pipe.ph, g_pipe.frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback_inline :: (userdata: *void, code: s64) -> bool callconv(.c) {
|
||||||
|
g_width = xx code;
|
||||||
|
g_height = xx (code + 1);
|
||||||
|
g_pipe.resize(xx g_width, xx g_height);
|
||||||
|
g_pipe.tick();
|
||||||
|
print("inline: pw={}, ph={}, frame={}\n", g_pipe.pw, g_pipe.ph, g_pipe.frame);
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback_wrapper :: (userdata: *void, code: s64) -> bool callconv(.c) {
|
||||||
|
g_width = xx code;
|
||||||
|
g_height = xx (code + 1);
|
||||||
|
do_render();
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
pipe := Pipe.{ pw = 0, ph = 0, frame = 0 };
|
||||||
|
g_pipe = @pipe;
|
||||||
|
|
||||||
|
callback_inline(xx 0, 320);
|
||||||
|
callback_wrapper(xx 0, 640);
|
||||||
|
}
|
||||||
89
examples/issue-0018.sx
Normal file
89
examples/issue-0018.sx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// issue-0018: Dot-shorthand `.{...}` for struct with protocol field causes
|
||||||
|
// LLVM verification error when used in List(T).append from 2+ different
|
||||||
|
// struct methods.
|
||||||
|
//
|
||||||
|
// Trigger: TWO or more structs each with `List(Container)` calling
|
||||||
|
// `.append(.{ child = d })` — using dot-shorthand.
|
||||||
|
//
|
||||||
|
// Works: Only 1 struct doing it, or using explicit `Container.{ child = d }`.
|
||||||
|
// Fails: 2+ structs → `Invalid InsertValueInst operands!`
|
||||||
|
// `%si = insertvalue i64 undef, { ptr, ptr } %load, 0`
|
||||||
|
//
|
||||||
|
// Likely a monomorphization issue in `List(T).append` when the dot-shorthand
|
||||||
|
// type inference is resolved from multiple call sites.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Drawable :: protocol {
|
||||||
|
draw :: () -> s32;
|
||||||
|
name :: () -> string;
|
||||||
|
layout :: (x: s32) -> s32;
|
||||||
|
handle :: (event: s32) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
Circle :: struct { radius: s32; }
|
||||||
|
impl Drawable for Circle {
|
||||||
|
draw :: (self: *Circle) -> s32 { self.radius; }
|
||||||
|
name :: (self: *Circle) -> string { "circle"; }
|
||||||
|
layout :: (self: *Circle, x: s32) -> s32 { x + self.radius; }
|
||||||
|
handle :: (self: *Circle, event: s32) -> bool { event > 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Square :: struct { side: s32; }
|
||||||
|
impl Drawable for Square {
|
||||||
|
draw :: (self: *Square) -> s32 { self.side * self.side; }
|
||||||
|
name :: (self: *Square) -> string { "square"; }
|
||||||
|
layout :: (self: *Square, x: s32) -> s32 { x + self.side; }
|
||||||
|
handle :: (self: *Square, event: s32) -> bool { event > 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect :: struct {
|
||||||
|
x: f32;
|
||||||
|
y: f32;
|
||||||
|
w: f32;
|
||||||
|
h: f32;
|
||||||
|
zero :: () -> Rect { Rect.{ x = 0.0, y = 0.0, w = 0.0, h = 0.0 }; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Container :: struct {
|
||||||
|
child: Drawable;
|
||||||
|
computed_frame: Rect = .zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two different structs, each with List(Container), both calling .append(.{...})
|
||||||
|
// This mirrors VStack/HStack in the game.
|
||||||
|
|
||||||
|
StackA :: struct {
|
||||||
|
children: List(Container);
|
||||||
|
|
||||||
|
add :: (self: *StackA, d: Drawable) {
|
||||||
|
// BUG: `.{ child = d }` causes LLVM error when 2+ structs do this
|
||||||
|
self.children.append(.{ child = d });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackB :: struct {
|
||||||
|
children: List(Container);
|
||||||
|
|
||||||
|
add :: (self: *StackB, d: Drawable) {
|
||||||
|
// BUG: second struct doing `.{ child = d }` triggers the error
|
||||||
|
self.children.append(.{ child = d });
|
||||||
|
// FIX: explicit `Container.{ child = d }` works
|
||||||
|
// self.children.append(Container.{ child = d });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> void {
|
||||||
|
c := Circle.{ radius = 42 };
|
||||||
|
s := Square.{ side = 5 };
|
||||||
|
|
||||||
|
a : StackA = .{};
|
||||||
|
a.add(c);
|
||||||
|
print("StackA: draw={}\n", a.children.items[0].child.draw());
|
||||||
|
|
||||||
|
b : StackB = .{};
|
||||||
|
b.add(s);
|
||||||
|
print("StackB: draw={}\n", b.children.items[0].child.draw());
|
||||||
|
|
||||||
|
print("OK\n");
|
||||||
|
}
|
||||||
42
examples/issue-0019.sx
Normal file
42
examples/issue-0019.sx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// issue-0019: #import c symbols are globally visible instead of scoped to the importing file
|
||||||
|
//
|
||||||
|
// When a file uses `#import c { #include "foo.h"; #source "foo.c"; }`,
|
||||||
|
// the C symbols become available to ALL files in the compilation unit,
|
||||||
|
// not just the file that imported them.
|
||||||
|
//
|
||||||
|
// This means a file can call C functions without importing the module
|
||||||
|
// that declares them, as long as some other file in the project does.
|
||||||
|
//
|
||||||
|
// Expected: C symbols from `#import c` should only be visible in files
|
||||||
|
// that directly (or transitively via SX #import) import the module.
|
||||||
|
//
|
||||||
|
// Repro:
|
||||||
|
// - a.sx: `#import c { #include "some_lib.h"; #source "some_lib.c"; };`
|
||||||
|
// - b.sx: does NOT import a.sx, but calls some_lib_function() — compiles successfully
|
||||||
|
//
|
||||||
|
// In the game project:
|
||||||
|
// - main.sx imports modules/stb_truetype.sx (which has #import c for kbts/stbtt)
|
||||||
|
// - ui/glyph_cache.sx does NOT import modules/stb_truetype.sx
|
||||||
|
// - ui/glyph_cache.sx calls kbts_ShapeRun, stbtt_InitFont, etc. — compiles fine
|
||||||
|
// - If main.sx removed the import, glyph_cache.sx would break
|
||||||
|
|
||||||
|
// Minimal repro structure (two files):
|
||||||
|
|
||||||
|
// --- module_with_c.sx ---
|
||||||
|
// #import c {
|
||||||
|
// #include "vendors/some_lib.h";
|
||||||
|
// #source "vendors/some_lib.c";
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// uses_c :: () -> s32 {
|
||||||
|
// some_lib_function();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// --- main.sx (this file) ---
|
||||||
|
// #import "module_with_c.sx";
|
||||||
|
//
|
||||||
|
// main :: () -> s32 {
|
||||||
|
// // This should fail because we never imported the C module directly,
|
||||||
|
// // but currently it compiles:
|
||||||
|
// some_lib_function();
|
||||||
|
// }
|
||||||
@@ -30,6 +30,7 @@ glClear : (u32) -> void = ---;
|
|||||||
glEnable : (u32) -> void = ---;
|
glEnable : (u32) -> void = ---;
|
||||||
glDisable : (u32) -> void = ---;
|
glDisable : (u32) -> void = ---;
|
||||||
glViewport : (s32, s32, s32, s32) -> void = ---;
|
glViewport : (s32, s32, s32, s32) -> void = ---;
|
||||||
|
glFlush : () -> void = ---;
|
||||||
glDrawArrays : (u32, s32, s32) -> void = ---;
|
glDrawArrays : (u32, s32, s32) -> void = ---;
|
||||||
glPolygonMode : (u32, u32) -> void = ---;
|
glPolygonMode : (u32, u32) -> void = ---;
|
||||||
glLineWidth : (f32) -> void = ---;
|
glLineWidth : (f32) -> void = ---;
|
||||||
@@ -101,6 +102,7 @@ load_gl :: (get_proc: ([*]u8) -> *void) {
|
|||||||
glEnable = xx get_proc("glEnable");
|
glEnable = xx get_proc("glEnable");
|
||||||
glDisable = xx get_proc("glDisable");
|
glDisable = xx get_proc("glDisable");
|
||||||
glViewport = xx get_proc("glViewport");
|
glViewport = xx get_proc("glViewport");
|
||||||
|
glFlush = xx get_proc("glFlush");
|
||||||
glDrawArrays = xx get_proc("glDrawArrays");
|
glDrawArrays = xx get_proc("glDrawArrays");
|
||||||
glPolygonMode = xx get_proc("glPolygonMode");
|
glPolygonMode = xx get_proc("glPolygonMode");
|
||||||
glLineWidth = xx get_proc("glLineWidth");
|
glLineWidth = xx get_proc("glLineWidth");
|
||||||
|
|||||||
@@ -332,12 +332,14 @@ SDL_GL_SwapWindow :: (window: *void) -> bool #foreign sdl3;
|
|||||||
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign sdl3;
|
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign sdl3;
|
||||||
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign sdl3;
|
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign sdl3;
|
||||||
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign sdl3;
|
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign sdl3;
|
||||||
|
SDL_AddEventWatch :: (filter: *void, userdata: *void) -> bool #foreign sdl3;
|
||||||
SDL_GetTicks :: () -> u64 #foreign sdl3;
|
SDL_GetTicks :: () -> u64 #foreign sdl3;
|
||||||
SDL_GetPerformanceCounter :: () -> u64 #foreign sdl3;
|
SDL_GetPerformanceCounter :: () -> u64 #foreign sdl3;
|
||||||
SDL_GetPerformanceFrequency :: () -> u64 #foreign sdl3;
|
SDL_GetPerformanceFrequency :: () -> u64 #foreign sdl3;
|
||||||
SDL_Delay :: (ms: u32) -> void #foreign sdl3;
|
SDL_Delay :: (ms: u32) -> void #foreign sdl3;
|
||||||
SDL_GetWindowDisplayScale :: (window: *void) -> f32 #foreign sdl3;
|
SDL_GetWindowDisplayScale :: (window: *void) -> f32 #foreign sdl3;
|
||||||
SDL_GetWindowSize :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
SDL_GetWindowSize :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
||||||
|
SDL_SetWindowSize :: (window: *void, w: s32, h: s32) -> bool #foreign sdl3;
|
||||||
SDL_GetWindowSizeInPixels :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
SDL_GetWindowSizeInPixels :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
||||||
|
|
||||||
SDL_Rect :: struct {
|
SDL_Rect :: struct {
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ pub const Root = struct {
|
|||||||
decls: []const *Node,
|
decls: []const *Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const CallingConvention = enum { default, c };
|
||||||
|
|
||||||
pub const FnDecl = struct {
|
pub const FnDecl = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
params: []const Param,
|
params: []const Param,
|
||||||
@@ -109,6 +111,7 @@ pub const FnDecl = struct {
|
|||||||
body: *Node,
|
body: *Node,
|
||||||
type_params: []const StructTypeParam = &.{},
|
type_params: []const StructTypeParam = &.{},
|
||||||
is_arrow: bool = false,
|
is_arrow: bool = false,
|
||||||
|
call_conv: CallingConvention = .default,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Param = struct {
|
pub const Param = struct {
|
||||||
@@ -323,6 +326,7 @@ pub const Lambda = struct {
|
|||||||
return_type: ?*Node,
|
return_type: ?*Node,
|
||||||
body: *Node,
|
body: *Node,
|
||||||
type_params: []const StructTypeParam = &.{},
|
type_params: []const StructTypeParam = &.{},
|
||||||
|
call_conv: CallingConvention = .default,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const TypeExpr = struct {
|
pub const TypeExpr = struct {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ pub const LLVMEmitter = struct {
|
|||||||
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
||||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||||
|
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty),
|
||||||
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
||||||
else => c.LLVMConstNull(llvm_ty),
|
else => c.LLVMConstNull(llvm_ty),
|
||||||
};
|
};
|
||||||
@@ -354,15 +355,16 @@ pub const LLVMEmitter = struct {
|
|||||||
|
|
||||||
// main always returns i32 at the LLVM level (JIT expects it)
|
// main always returns i32 at the LLVM level (JIT expects it)
|
||||||
const raw_ret_ty = self.toLLVMType(func.ret);
|
const raw_ret_ty = self.toLLVMType(func.ret);
|
||||||
const ret_ty = if (is_main) self.cached_i32 else if (func.is_extern) self.abiCoerceParamType(func.ret, raw_ret_ty) else raw_ret_ty;
|
const needs_c_abi = func.is_extern or func.call_conv == .c;
|
||||||
|
const ret_ty = if (is_main) self.cached_i32 else if (needs_c_abi) self.abiCoerceParamType(func.ret, raw_ret_ty) else raw_ret_ty;
|
||||||
|
|
||||||
// Build parameter types — apply C ABI coercion for foreign (extern) functions
|
// Build parameter types — apply C ABI coercion for foreign/callconv(.c) functions
|
||||||
const param_count: c_uint = @intCast(func.params.len);
|
const param_count: c_uint = @intCast(func.params.len);
|
||||||
const param_types = self.alloc.alloc(c.LLVMTypeRef, func.params.len) catch unreachable;
|
const param_types = self.alloc.alloc(c.LLVMTypeRef, func.params.len) catch unreachable;
|
||||||
defer self.alloc.free(param_types);
|
defer self.alloc.free(param_types);
|
||||||
for (func.params, 0..) |param, j| {
|
for (func.params, 0..) |param, j| {
|
||||||
const llvm_ty = self.toLLVMType(param.ty);
|
const llvm_ty = self.toLLVMType(param.ty);
|
||||||
param_types[j] = if (func.is_extern) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty;
|
param_types[j] = if (needs_c_abi) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn_type = c.LLVMFunctionType(ret_ty, param_types.ptr, param_count, 0);
|
const fn_type = c.LLVMFunctionType(ret_ty, param_types.ptr, param_count, 0);
|
||||||
@@ -378,6 +380,11 @@ pub const LLVMEmitter = struct {
|
|||||||
.private => c.LLVMSetLinkage(llvm_func, c.LLVMPrivateLinkage),
|
.private => c.LLVMSetLinkage(llvm_func, c.LLVMPrivateLinkage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set calling convention
|
||||||
|
if (func.call_conv == .c) {
|
||||||
|
c.LLVMSetFunctionCallConv(llvm_func, c.LLVMCCallConv);
|
||||||
|
}
|
||||||
|
|
||||||
// Add frame-pointer and nounwind attributes for correct ARM64 codegen
|
// Add frame-pointer and nounwind attributes for correct ARM64 codegen
|
||||||
{
|
{
|
||||||
const fp_kind = "frame-pointer";
|
const fp_kind = "frame-pointer";
|
||||||
@@ -2840,6 +2847,23 @@ pub const LLVMEmitter = struct {
|
|||||||
return c.LLVMConstStructInContext(self.context, &fields, 2, 0);
|
return c.LLVMConstStructInContext(self.context, &fields, 2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emitConstAggregate(self: *LLVMEmitter, agg: []const ir_inst.ConstantValue, llvm_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
||||||
|
const elem_ty = c.LLVMGetElementType(llvm_ty);
|
||||||
|
const n: c_uint = @intCast(agg.len);
|
||||||
|
const vals = self.alloc.alloc(c.LLVMValueRef, agg.len) catch return c.LLVMConstNull(llvm_ty);
|
||||||
|
defer self.alloc.free(vals);
|
||||||
|
for (agg, 0..) |cv, i| {
|
||||||
|
vals[i] = switch (cv) {
|
||||||
|
.int => |v| c.LLVMConstInt(elem_ty, @bitCast(v), 1),
|
||||||
|
.float => |v| c.LLVMConstReal(elem_ty, v),
|
||||||
|
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
||||||
|
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
||||||
|
else => c.LLVMConstNull(elem_ty),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return c.LLVMConstArray(elem_ty, vals.ptr, n);
|
||||||
|
}
|
||||||
|
|
||||||
fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef {
|
fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef {
|
||||||
// LLVMBuildGlobalStringPtr needs a null-terminated C string
|
// LLVMBuildGlobalStringPtr needs a null-terminated C string
|
||||||
const str_z = self.alloc.dupeZ(u8, str) catch unreachable;
|
const str_z = self.alloc.dupeZ(u8, str) catch unreachable;
|
||||||
|
|||||||
@@ -410,6 +410,7 @@ pub const Function = struct {
|
|||||||
is_extern: bool = false,
|
is_extern: bool = false,
|
||||||
is_comptime: bool = false,
|
is_comptime: bool = false,
|
||||||
linkage: Linkage = .internal,
|
linkage: Linkage = .internal,
|
||||||
|
call_conv: CallingConvention = .default,
|
||||||
|
|
||||||
pub const Param = struct {
|
pub const Param = struct {
|
||||||
name: StringId,
|
name: StringId,
|
||||||
@@ -422,6 +423,11 @@ pub const Function = struct {
|
|||||||
private,
|
private,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const CallingConvention = enum {
|
||||||
|
default,
|
||||||
|
c,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init(name: StringId, params: []const Param, ret: TypeId) Function {
|
pub fn init(name: StringId, params: []const Param, ret: TypeId) Function {
|
||||||
return .{
|
return .{
|
||||||
.name = name,
|
.name = name,
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ pub const Lowering = struct {
|
|||||||
.bool_literal => |bl| .{ .boolean = bl.value },
|
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||||
.float_literal => |fl| .{ .float = fl.value },
|
.float_literal => |fl| .{ .float = fl.value },
|
||||||
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||||
|
.array_literal => |al| self.constArrayLiteral(al.elements),
|
||||||
else => null,
|
else => null,
|
||||||
} else null;
|
} else null;
|
||||||
const gid = self.module.addGlobal(.{
|
const gid = self.module.addGlobal(.{
|
||||||
@@ -467,6 +468,31 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to convert an array literal's elements into a compile-time ConstantValue.aggregate.
|
||||||
|
/// Returns null if any element is not a compile-time constant.
|
||||||
|
fn constArrayLiteral(self: *Lowering, elements: []const *const Node) ?inst_mod.ConstantValue {
|
||||||
|
const vals = self.alloc.alloc(inst_mod.ConstantValue, elements.len) catch return null;
|
||||||
|
for (elements, 0..) |elem, i| {
|
||||||
|
vals[i] = switch (elem.data) {
|
||||||
|
.int_literal => |il| .{ .int = il.value },
|
||||||
|
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||||
|
.float_literal => |fl| .{ .float = fl.value },
|
||||||
|
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||||
|
.unary_op => |uo| switch (uo.op) {
|
||||||
|
.negate => switch (uo.operand.data) {
|
||||||
|
.int_literal => |il| .{ .int = -il.value },
|
||||||
|
.float_literal => |fl| .{ .float = -fl.value },
|
||||||
|
else => return null,
|
||||||
|
},
|
||||||
|
else => return null,
|
||||||
|
},
|
||||||
|
.array_literal => |al| self.constArrayLiteral(al.elements) orelse return null,
|
||||||
|
else => return null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return .{ .aggregate = vals };
|
||||||
|
}
|
||||||
|
|
||||||
/// Pass 2: Lower main function body and comptime side-effects.
|
/// Pass 2: Lower main function body and comptime side-effects.
|
||||||
fn lowerMainAndComptime(self: *Lowering, decls: []const *const Node) void {
|
fn lowerMainAndComptime(self: *Lowering, decls: []const *const Node) void {
|
||||||
for (decls) |decl| {
|
for (decls) |decl| {
|
||||||
@@ -514,19 +540,23 @@ pub const Lowering = struct {
|
|||||||
}) catch unreachable;
|
}) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cc: Function.CallingConvention = if (fd.call_conv == .c) .c else .default;
|
||||||
|
|
||||||
// For #foreign with C name override, declare under C name and map sx name → C name
|
// For #foreign with C name override, declare under C name and map sx name → C name
|
||||||
if (fd.body.data == .foreign_expr) {
|
if (fd.body.data == .foreign_expr) {
|
||||||
const fe = fd.body.data.foreign_expr;
|
const fe = fd.body.data.foreign_expr;
|
||||||
if (fe.c_name) |c_name| {
|
if (fe.c_name) |c_name| {
|
||||||
const c_name_id = self.module.types.internString(c_name);
|
const c_name_id = self.module.types.internString(c_name);
|
||||||
_ = self.builder.declareExtern(c_name_id, params.items, ret_ty);
|
const fid = self.builder.declareExtern(c_name_id, params.items, ret_ty);
|
||||||
|
self.module.getFunctionMut(fid).call_conv = cc;
|
||||||
self.foreign_name_map.put(name, c_name) catch {};
|
self.foreign_name_map.put(name, c_name) catch {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const name_id = self.module.types.internString(name);
|
const name_id = self.module.types.internString(name);
|
||||||
_ = self.builder.declareExtern(name_id, params.items, ret_ty);
|
const fid = self.builder.declareExtern(name_id, params.items, ret_ty);
|
||||||
|
self.module.getFunctionMut(fid).call_conv = cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lazily lower a function body on demand. Called when lowerCall can't find
|
/// Lazily lower a function body on demand. Called when lowerCall can't find
|
||||||
@@ -607,6 +637,7 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
func.is_extern = false; // promote from extern stub to real function
|
func.is_extern = false; // promote from extern stub to real function
|
||||||
func.linkage = if (std.mem.eql(u8, name, "main")) .external else .internal;
|
func.linkage = if (std.mem.eql(u8, name, "main")) .external else .internal;
|
||||||
|
if (fd.call_conv == .c) func.call_conv = .c;
|
||||||
// Set inst_counter to param count (params occupy refs 0..N-1)
|
// Set inst_counter to param count (params occupy refs 0..N-1)
|
||||||
std.debug.assert(func.params.len == fd.params.len); // AST and IR param counts must match
|
std.debug.assert(func.params.len == fd.params.len); // AST and IR param counts must match
|
||||||
self.builder.inst_counter = @intCast(func.params.len);
|
self.builder.inst_counter = @intCast(func.params.len);
|
||||||
@@ -717,6 +748,11 @@ pub const Lowering = struct {
|
|||||||
self.builder.currentFunc().linkage = .external;
|
self.builder.currentFunc().linkage = .external;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set calling convention
|
||||||
|
if (fd.call_conv == .c) {
|
||||||
|
self.builder.currentFunc().call_conv = .c;
|
||||||
|
}
|
||||||
|
|
||||||
// Create entry block
|
// Create entry block
|
||||||
const entry_name = self.module.types.internString("entry");
|
const entry_name = self.module.types.internString("entry");
|
||||||
const entry = self.builder.appendBlock(entry_name, &.{});
|
const entry = self.builder.appendBlock(entry_name, &.{});
|
||||||
@@ -1070,12 +1106,14 @@ pub const Lowering = struct {
|
|||||||
// Set target_type from LHS for RHS lowering (enum literals, struct literals, etc.)
|
// Set target_type from LHS for RHS lowering (enum literals, struct literals, etc.)
|
||||||
const old_target = self.target_type;
|
const old_target = self.target_type;
|
||||||
if (asgn.target.data == .identifier) {
|
if (asgn.target.data == .identifier) {
|
||||||
|
var found_local = false;
|
||||||
if (self.scope) |scope| {
|
if (self.scope) |scope| {
|
||||||
if (scope.lookup(asgn.target.data.identifier.name)) |binding| {
|
if (scope.lookup(asgn.target.data.identifier.name)) |binding| {
|
||||||
self.target_type = binding.ty;
|
self.target_type = binding.ty;
|
||||||
|
found_local = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (self.target_type == null) {
|
if (!found_local) {
|
||||||
if (self.global_names.get(asgn.target.data.identifier.name)) |gi| {
|
if (self.global_names.get(asgn.target.data.identifier.name)) |gi| {
|
||||||
self.target_type = gi.ty;
|
self.target_type = gi.ty;
|
||||||
}
|
}
|
||||||
@@ -4387,6 +4425,9 @@ pub const Lowering = struct {
|
|||||||
};
|
};
|
||||||
const name_id = self.module.types.internString(name);
|
const name_id = self.module.types.internString(name);
|
||||||
const func_id = self.builder.beginFunction(name_id, params.items, ret_ty);
|
const func_id = self.builder.beginFunction(name_id, params.items, ret_ty);
|
||||||
|
if (lam.call_conv == .c) {
|
||||||
|
self.module.getFunctionMut(func_id).call_conv = .c;
|
||||||
|
}
|
||||||
|
|
||||||
// Create entry block
|
// Create entry block
|
||||||
const entry_name = self.module.types.internString("entry");
|
const entry_name = self.module.types.internString("entry");
|
||||||
@@ -6436,6 +6477,58 @@ pub const Lowering = struct {
|
|||||||
/// Resolve parameter types for a call expression (for target_type context).
|
/// Resolve parameter types for a call expression (for target_type context).
|
||||||
/// Returns empty slice if the function can't be resolved.
|
/// Returns empty slice if the function can't be resolved.
|
||||||
fn resolveCallParamTypes(self: *Lowering, c: *const ast.Call) []const TypeId {
|
fn resolveCallParamTypes(self: *Lowering, c: *const ast.Call) []const TypeId {
|
||||||
|
// Method calls: obj.method(args) — resolve param types from the method signature,
|
||||||
|
// skipping the first param (self) since it's prepended later.
|
||||||
|
if (c.callee.data == .field_access) {
|
||||||
|
const fa = c.callee.data.field_access;
|
||||||
|
const obj_ty = self.inferExprType(fa.object);
|
||||||
|
if (self.getStructTypeName(obj_ty)) |sname| {
|
||||||
|
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch return &.{};
|
||||||
|
// Try already-lowered functions first
|
||||||
|
if (self.resolveFuncByName(qualified)) |fid| {
|
||||||
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
|
if (func.params.len > 0) {
|
||||||
|
// Skip self param — caller args don't include self
|
||||||
|
var types_list = std.ArrayList(TypeId).empty;
|
||||||
|
for (func.params[1..]) |p| {
|
||||||
|
types_list.append(self.alloc, p.ty) catch unreachable;
|
||||||
|
}
|
||||||
|
return types_list.items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try AST map (not yet lowered)
|
||||||
|
if (self.fn_ast_map.get(qualified)) |fd| {
|
||||||
|
if (fd.params.len > 0) {
|
||||||
|
var types_list = std.ArrayList(TypeId).empty;
|
||||||
|
for (fd.params[1..]) |p| {
|
||||||
|
types_list.append(self.alloc, self.resolveParamType(&p)) catch unreachable;
|
||||||
|
}
|
||||||
|
return types_list.items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try generic struct template method: List__Container.append → List.append
|
||||||
|
// with type bindings from the struct instantiation
|
||||||
|
if (self.struct_instance_template.get(sname)) |tmpl_name| {
|
||||||
|
const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, fa.field }) catch return &.{};
|
||||||
|
if (self.fn_ast_map.get(tmpl_qualified)) |fd| {
|
||||||
|
if (fd.params.len > 0) {
|
||||||
|
// Temporarily set type_bindings so resolveParamType can substitute T → concrete type
|
||||||
|
const saved_bindings = self.type_bindings;
|
||||||
|
if (self.struct_instance_bindings.getPtr(sname)) |bindings| {
|
||||||
|
self.type_bindings = bindings.*;
|
||||||
|
}
|
||||||
|
var types_list = std.ArrayList(TypeId).empty;
|
||||||
|
for (fd.params[1..]) |p| {
|
||||||
|
types_list.append(self.alloc, self.resolveParamType(&p)) catch unreachable;
|
||||||
|
}
|
||||||
|
self.type_bindings = saved_bindings;
|
||||||
|
return types_list.items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &.{};
|
||||||
|
}
|
||||||
if (c.callee.data != .identifier) return &.{};
|
if (c.callee.data != .identifier) return &.{};
|
||||||
const bare_name = c.callee.data.identifier.name;
|
const bare_name = c.callee.data.identifier.name;
|
||||||
const name = blk: {
|
const name = blk: {
|
||||||
|
|||||||
@@ -1478,6 +1478,7 @@ pub const Server = struct {
|
|||||||
.kw_protocol,
|
.kw_protocol,
|
||||||
.kw_impl,
|
.kw_impl,
|
||||||
.kw_inline,
|
.kw_inline,
|
||||||
|
.kw_callconv,
|
||||||
.hash_run,
|
.hash_run,
|
||||||
.hash_import,
|
.hash_import,
|
||||||
.hash_insert,
|
.hash_insert,
|
||||||
|
|||||||
@@ -1220,6 +1220,24 @@ pub const Parser = struct {
|
|||||||
return_type = try self.parseTypeExpr();
|
return_type = try self.parseTypeExpr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional calling convention: callconv(.c)
|
||||||
|
var call_conv: ast.CallingConvention = .default;
|
||||||
|
if (self.current.tag == .kw_callconv) {
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.l_paren);
|
||||||
|
try self.expect(.dot);
|
||||||
|
if (self.current.tag != .identifier)
|
||||||
|
return self.fail("expected calling convention name after '.'");
|
||||||
|
const cc_name = self.tokenSlice(self.current);
|
||||||
|
if (std.mem.eql(u8, cc_name, "c")) {
|
||||||
|
call_conv = .c;
|
||||||
|
} else {
|
||||||
|
return self.fail("unknown calling convention");
|
||||||
|
}
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.r_paren);
|
||||||
|
}
|
||||||
|
|
||||||
// Body: block `{ ... }`, arrow `=> expr;`, #builtin, #compiler, or #foreign marker
|
// Body: block `{ ... }`, arrow `=> expr;`, #builtin, #compiler, or #foreign marker
|
||||||
var is_arrow = false;
|
var is_arrow = false;
|
||||||
const body = if (self.current.tag == .hash_builtin) blk: {
|
const body = if (self.current.tag == .hash_builtin) blk: {
|
||||||
@@ -1273,6 +1291,7 @@ pub const Parser = struct {
|
|||||||
.body = body,
|
.body = body,
|
||||||
.type_params = type_params,
|
.type_params = type_params,
|
||||||
.is_arrow = is_arrow,
|
.is_arrow = is_arrow,
|
||||||
|
.call_conv = call_conv,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2333,6 +2352,24 @@ pub const Parser = struct {
|
|||||||
return_type = try self.parseTypeExpr();
|
return_type = try self.parseTypeExpr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional calling convention: callconv(.c)
|
||||||
|
var call_conv: ast.CallingConvention = .default;
|
||||||
|
if (self.current.tag == .kw_callconv) {
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.l_paren);
|
||||||
|
try self.expect(.dot);
|
||||||
|
if (self.current.tag != .identifier)
|
||||||
|
return self.fail("expected calling convention name after '.'");
|
||||||
|
const cc_name = self.tokenSlice(self.current);
|
||||||
|
if (std.mem.eql(u8, cc_name, "c")) {
|
||||||
|
call_conv = .c;
|
||||||
|
} else {
|
||||||
|
return self.fail("unknown calling convention");
|
||||||
|
}
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.r_paren);
|
||||||
|
}
|
||||||
|
|
||||||
// Two body forms:
|
// Two body forms:
|
||||||
// (params) => expr — expression lambda
|
// (params) => expr — expression lambda
|
||||||
// (params) { stmts } — block-body lambda
|
// (params) { stmts } — block-body lambda
|
||||||
@@ -2348,6 +2385,7 @@ pub const Parser = struct {
|
|||||||
.return_type = return_type,
|
.return_type = return_type,
|
||||||
.body = body,
|
.body = body,
|
||||||
.type_params = type_params,
|
.type_params = type_params,
|
||||||
|
.call_conv = call_conv,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2365,7 +2403,7 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
fn isFunctionDef(self: *Parser) bool {
|
fn isFunctionDef(self: *Parser) bool {
|
||||||
const tag = self.peekPastParens() orelse return false;
|
const tag = self.peekPastParens() orelse return false;
|
||||||
return tag == .l_brace or tag == .arrow or tag == .hash_builtin or tag == .hash_compiler or tag == .hash_foreign or tag == .fat_arrow;
|
return tag == .l_brace or tag == .arrow or tag == .hash_builtin or tag == .hash_compiler or tag == .hash_foreign or tag == .fat_arrow or tag == .kw_callconv;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isAssignOp(self: *const Parser) bool {
|
fn isAssignOp(self: *const Parser) bool {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ pub const Tag = enum {
|
|||||||
kw_impl, // impl
|
kw_impl, // impl
|
||||||
kw_Self, // Self (in protocol declarations)
|
kw_Self, // Self (in protocol declarations)
|
||||||
kw_inline, // inline (compile-time if/for/while)
|
kw_inline, // inline (compile-time if/for/while)
|
||||||
|
kw_callconv, // callconv (calling convention annotation)
|
||||||
|
|
||||||
// Symbols
|
// Symbols
|
||||||
colon, // :
|
colon, // :
|
||||||
@@ -227,6 +228,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
|||||||
.{ "impl", .kw_impl },
|
.{ "impl", .kw_impl },
|
||||||
.{ "Self", .kw_Self },
|
.{ "Self", .kw_Self },
|
||||||
.{ "inline", .kw_inline },
|
.{ "inline", .kw_inline },
|
||||||
|
.{ "callconv", .kw_callconv },
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn getKeyword(bytes: []const u8) ?Tag {
|
pub fn getKeyword(bytes: []const u8) ?Tag {
|
||||||
|
|||||||
1
tests/expected/51-compound-assign-global.exit
Normal file
1
tests/expected/51-compound-assign-global.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
10
tests/expected/51-compound-assign-global.txt
Normal file
10
tests/expected/51-compound-assign-global.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
add: 13
|
||||||
|
sub: 5
|
||||||
|
mul: 60
|
||||||
|
div: 5
|
||||||
|
mod: 1
|
||||||
|
and: 15
|
||||||
|
or: 255
|
||||||
|
xor: 240
|
||||||
|
shl: 64
|
||||||
|
shr: 8
|
||||||
1
tests/expected/issue-0012.exit
Normal file
1
tests/expected/issue-0012.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
6
tests/expected/issue-0012.txt
Normal file
6
tests/expected/issue-0012.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ok: floating is null
|
||||||
|
ok: left h=0
|
||||||
|
ok: center h=1
|
||||||
|
ok: top_right h=2 v=0
|
||||||
|
rect
|
||||||
|
text
|
||||||
1
tests/expected/issue-0016.exit
Normal file
1
tests/expected/issue-0016.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
tests/expected/issue-0016.txt
Normal file
1
tests/expected/issue-0016.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
callconv(.c): 42
|
||||||
1
tests/expected/issue-0017.exit
Normal file
1
tests/expected/issue-0017.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
2
tests/expected/issue-0017.txt
Normal file
2
tests/expected/issue-0017.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
inline: pw=320, ph=321, frame=1
|
||||||
|
wrapper: pw=640, ph=641, frame=2
|
||||||
Reference in New Issue
Block a user