From 25e1372731fa35008a2e0e2339d3429ab8ae7251 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 11 Feb 2026 01:05:21 +0200 Subject: [PATCH] extend default to s64 --- examples/11-vector-math.sx | 2 +- examples/12-meta.sx | 19 +- examples/21-categories.sx | 3 + examples/23-quicksort.sx | 10 +- examples/24-list.sx | 12 ++ examples/25-slices.sx | 31 +++ examples/modules/std.sx | 91 +++++++-- specs.md | 32 +-- src/codegen.zig | 345 ++++++++++++++++++++------------- src/comptime.zig | 387 ++++++++++++++++++++++++++++++++++--- src/sema.zig | 30 +-- 11 files changed, 754 insertions(+), 208 deletions(-) create mode 100644 examples/24-list.sx create mode 100644 examples/25-slices.sx diff --git a/examples/11-vector-math.sx b/examples/11-vector-math.sx index 19da380..cbc0f9e 100644 --- a/examples/11-vector-math.sx +++ b/examples/11-vector-math.sx @@ -1,5 +1,5 @@ #import "modules/std.sx"; -math :: #import "modules/std/math.sx"; +math :: #import "modules/math.sx"; vec3 :: (x:f32, y:f32, z:f32) -> Vector(3,f32) { .[x, y, z]; diff --git a/examples/12-meta.sx b/examples/12-meta.sx index b8601c6..d491583 100644 --- a/examples/12-meta.sx +++ b/examples/12-meta.sx @@ -1,12 +1,27 @@ #import "modules/std.sx"; #import "modules/math.sx"; +test :: () -> s32 { + 0; +} + +Vec4 :: Vector(4,f32); + main :: () { - x:Type = f64; + x : Type = f64; v:f64 = 3.2; print("{}\n", x); print("{}\n", v); - x= Vec4; + x = Vec4; + print("{}\n", x); + + x = test; print("{}\n", x); } + +// ** stdout ** +// f64 +// 3.200000 +// Vec4 +// () -> s32 \ No newline at end of file diff --git a/examples/21-categories.sx b/examples/21-categories.sx index c2a5672..205f7f5 100644 --- a/examples/21-categories.sx +++ b/examples/21-categories.sx @@ -11,9 +11,12 @@ Color :: struct { main :: () { p := Point.{10, 20}; c := Color.{255, 128, 0}; + pc := &p; print("p: {}\n", p); print("c: {}\n", c); print("n: {}\n", 42); print("s: {}\n", "hello"); print("b: {}\n", true); + print("&p: {}\n", pc); + print("&p: {}\n", &p); } diff --git a/examples/23-quicksort.sx b/examples/23-quicksort.sx index bc4ef9d..d986370 100644 --- a/examples/23-quicksort.sx +++ b/examples/23-quicksort.sx @@ -1,7 +1,7 @@ #import "modules/std.sx"; -quickSort :: (items: []$T) { - partition :: (items: []T, lo: s32, hi: s32) -> s32 { +quick_sort :: (items: []$T) { + partition :: (items: []T, lo: s64, hi: s64) -> s64 { pivot := items[hi]; i := lo - 1; j := lo; @@ -21,7 +21,7 @@ quickSort :: (items: []$T) { i; } - sort :: (items: []T, lo: s32, hi: s32) { + sort :: (items: []T, lo: s64, hi: s64) { if lo < hi { pi := partition(items, lo, hi); sort(items, lo, pi - 1); @@ -33,7 +33,7 @@ quickSort :: (items: []$T) { } main :: () { - arr : []s32 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1]; - quickSort(arr); + arr : []s64 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1]; + quick_sort(arr); print("{}\n", arr); } diff --git a/examples/24-list.sx b/examples/24-list.sx new file mode 100644 index 0000000..f14c527 --- /dev/null +++ b/examples/24-list.sx @@ -0,0 +1,12 @@ +#import "modules/std.sx"; + +List :: struct ($T: Type) { + items: [*]T = .[]; + len: s32 = 0; + cap: s32 = 0; +} + +main :: () { + list := List(s32).{}; + print("{}\n", list); +} \ No newline at end of file diff --git a/examples/25-slices.sx b/examples/25-slices.sx new file mode 100644 index 0000000..c534a78 --- /dev/null +++ b/examples/25-slices.sx @@ -0,0 +1,31 @@ +#import "modules/std.sx"; + +main :: () { + arr : [5]s32 = .[3, 1, 4, 1, 5]; + print("arr.len = {}\n", arr.len); + + // subslice array + sub := arr[1..4]; + print("arr[1..4] = {}\n", sub); + print("sub.len = {}\n", sub.len); + + // open-ended + head := arr[..3]; + tail := arr[2..]; + print("arr[..3] = {}\n", head); + print("arr[2..] = {}\n", tail); + + // slice of slice + sl : []s32 = .[10, 20, 30, 40, 50]; + mid := sl[1..4]; + print("sl[1..4] = {}\n", mid); + rest := mid[1..]; + print("mid[1..] = {}\n", rest); + + // string subslicing + msg := "hello world"; + word := msg[6..11]; + print("msg[6..11] = {}\n", word); + prefix := msg[..5]; + print("msg[..5] = {}\n", prefix); +} diff --git a/examples/modules/std.sx b/examples/modules/std.sx index dd65f37..9fefe96 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -1,15 +1,15 @@ Vector :: ($N: int, $T: Type) -> Type #builtin; write :: (str: string) -> void #builtin; sqrt :: (x: $T) -> T #builtin; -size_of :: ($T: Type) -> s32 #builtin; -alloc :: (size: s32) -> string #builtin; +size_of :: ($T: Type) -> s64 #builtin; +alloc :: (size: s64) -> string #builtin; type_of :: (val: $T) -> Type #builtin; type_name :: ($T: Type) -> string #builtin; -field_count :: ($T: Type) -> s32 #builtin; -field_name :: ($T: Type, idx: s32) -> string #builtin; -field_value :: (s: $T, idx: s32) -> Any #builtin; +field_count :: ($T: Type) -> s64 #builtin; +field_name :: ($T: Type, idx: s64) -> string #builtin; +field_value :: (s: $T, idx: s64) -> Any #builtin; -int_to_string :: (n: s32) -> string { +int_to_string :: (n: s64) -> string { if n == 0 { return "0"; } neg := n < 0; v := if neg then 0 - n else n; @@ -35,8 +35,8 @@ bool_to_string :: (b: bool) -> string { float_to_string :: (f: f64) -> string { neg := f < 0.0; v := if neg then 0.0 - f else f; - int_part := cast(s32) v; - frac := cast(s32) ((v - cast(f64) int_part) * 1000000.0); + int_part := cast(s64) v; + frac := cast(s64) ((v - cast(f64) int_part) * 1000000.0); if frac < 0 { frac = 0 - frac; } istr := int_to_string(int_part); fstr := int_to_string(frac); @@ -59,6 +59,68 @@ float_to_string :: (f: f64) -> string { buf; } +int_to_hex_string :: (n: s64) -> string { + if n == 0 { return "0"; } + + // Split into four 16-bit groups for correct unsigned treatment + g0 := n % 65536; + if g0 < 0 { g0 = g0 + 65536; } + r1 := (n - g0) / 65536; + g1 := r1 % 65536; + if g1 < 0 { g1 = g1 + 65536; } + r2 := (r1 - g1) / 65536; + g2 := r2 % 65536; + if g2 < 0 { g2 = g2 + 65536; } + r3 := (r2 - g2) / 65536; + g3 := r3 % 65536; + if g3 < 0 { g3 = g3 + 65536; } + + buf := alloc(16); + // Group 3: digits 0-3 (bits 48-63) + i := 3; + v := g3; + while i >= 0 { + d := v % 16; + buf[i] = if d < 10 then d + 48 else d - 10 + 97; + v = v / 16; + i -= 1; + } + // Group 2: digits 4-7 (bits 32-47) + i = 7; + v = g2; + while i >= 4 { + d := v % 16; + buf[i] = if d < 10 then d + 48 else d - 10 + 97; + v = v / 16; + i -= 1; + } + // Group 1: digits 8-11 (bits 16-31) + i = 11; + v = g1; + while i >= 8 { + d := v % 16; + buf[i] = if d < 10 then d + 48 else d - 10 + 97; + v = v / 16; + i -= 1; + } + // Group 0: digits 12-15 (bits 0-15) + i = 15; + v = g0; + while i >= 12 { + d := v % 16; + buf[i] = if d < 10 then d + 48 else d - 10 + 97; + v = v / 16; + i -= 1; + } + // Skip leading zeros (keep at least 1 digit) + start := 0; + while start < 15 { + if buf[start] != 48 { break; } + start += 1; + } + substr(buf, start, 16 - start); +} + concat :: (a: string, b: string) -> string { al := a.len; bl := b.len; @@ -70,7 +132,7 @@ concat :: (a: string, b: string) -> string { buf; } -substr :: (s: string, start: s32, len: s32) -> string { +substr :: (s: string, start: s64, len: s64) -> string { buf := alloc(len); i := 0; while i < len { @@ -94,7 +156,7 @@ struct_to_string :: (s: $T) -> string { } enum_to_string :: (e: $T) -> string { - concat(".", field_name(T, cast(s32) e)); + concat(".", field_name(T, cast(s64) e)); } vector_to_string :: (v: $T) -> string { @@ -130,8 +192,13 @@ slice_to_string :: (items: []$T) -> string { concat(result, "]"); } +pointer_to_string :: (p: $T) -> string { + addr : s64 = xx p; + concat(type_name(T), concat("@", int_to_hex_string(addr))); +} + union_to_string :: (u: $T) -> string { - tag := cast(s32) u; + tag := cast(s64) u; result := concat(".", field_name(T, tag)); payload := field_value(u, tag); pstr := any_to_string(payload); @@ -156,6 +223,8 @@ any_to_string :: (val: Any) -> string { case array: result = array_to_string(cast(type) val); case slice: result = slice_to_string(cast(type) val); case union: result = union_to_string(cast(type) val); + case pointer: result = pointer_to_string(cast(type) val); + case type: { s : string = xx val; result = s; } } result; } diff --git a/specs.md b/specs.md index 1ac8d66..3238b36 100644 --- a/specs.md +++ b/specs.md @@ -17,7 +17,7 @@ Line comments start with `//` and extend to end of line. | Kind | Examples | Type | |-----------|---------------------|---------| -| Integer | `0`, `42`, `0xFF`, `0b1010` | `s32` | +| Integer | `0`, `42`, `0xFF`, `0b1010` | `s64` | | Float | `0.3`, `0.9` | `f32` | | String | `"Hello"`, `"z: {z}"` | `string` | | Boolean | `true`, `false` | `bool` | @@ -71,14 +71,14 @@ Line comments start with `//` and extend to end of line. ## 2. Type System ### Primitive Types -- `s1`..`s64` — signed integers (1 to 64 bits). `s32` is the default for integer literals. +- `s1`..`s64` — signed integers (1 to 64 bits). `s64` is the default for integer literals. - `u1`..`u64` — unsigned integers (1 to 64 bits). - `f32` — 32-bit floating point - `f64` — 64-bit floating point - `bool` — boolean (`true` / `false`) - `string` — string of characters -- `Any` — type-erased value, represented as `{ i32, i64 }` (type tag + payload). Used for variadic arguments and runtime type dispatch. -- `Type` — compile-time type value. At runtime, represented as an `i32` type tag (same tag space as `Any`). +- `Any` — type-erased value, represented as `{ i64, i64 }` (type tag + payload). Used for variadic arguments and runtime type dispatch. +- `Type` — compile-time type value. At runtime, represented as an `i64` type tag (same tag space as `Any`). ### Enum Types User-defined sum types with named variants. @@ -139,7 +139,7 @@ print("{}", v1); // Vec4{x:1.0, y:2.0, z:3.0, w:0.0} ``` ### Union Types (Tagged Unions) -Sum types where each variant can carry typed data or be void. Internally represented as `{ i32, [max_payload_size x i8] }`. +Sum types where each variant can carry typed data or be void. Internally represented as `{ i64, [max_payload_size x i8] }`. #### Declaration ```sx @@ -182,7 +182,7 @@ Fixed-size arrays with element type and length. ```sx buffer : [5]f32 = .[0, 2, 3.5, 4, 0]; val := buffer[2]; // 3.5 -buffer.len // 5 (compile-time constant, s32) +buffer.len // 5 (compile-time constant, s64) ``` Arrays can also be constructed programmatically with the `Array` builtin: @@ -191,7 +191,7 @@ MyArr :: Array(5, s32); // equivalent to [5]s32 ``` ### Slice Types -A slice `[]T` is a fat pointer `{ptr, i32}` referencing a contiguous sequence of `T` elements. Same runtime layout as `string`. +A slice `[]T` is a fat pointer `{ptr, i64}` referencing a contiguous sequence of `T` elements. Same runtime layout as `string`. ```sx // Arrays implicitly coerce to slices at call sites arr : [5]s32 = .[3, 1, 4, 1, 5]; @@ -200,7 +200,7 @@ sortSlice(arr); // [5]s32 → []s32 coercion // Slice operations items[i] // read element at index items[i] = val; // write element at index -items.len // length (s32) +items.len // length (s64) items.ptr // raw pointer ``` @@ -352,7 +352,7 @@ print :: (fmt: string, args: ..Any) { ... } - `::` bindings infer type from the right-hand side - `:=` bindings infer type from the right-hand side - Explicit annotation overrides inference: `NAME : f64 : 0.9;` -- Integer literals default to `s32` +- Integer literals default to `s64` - Float literals default to `f32` - Enum literals (`.variant`) infer their enum type from context (expected type) @@ -609,13 +609,13 @@ while i < 10 { ```sx for iterable { // `it` is the current element - // `it_index` is the current index (s32) + // `it_index` is the current index (s64) print("{it}\n"); } ``` Iterates over arrays and slices. The loop body has two implicit variables: - `it` — the current element value -- `it_index` — the current index (s32, starting at 0) +- `it_index` — the current index (s64, starting at 0) `break;` exits the loop. `continue;` skips to the next iteration. ```sx @@ -730,15 +730,15 @@ Built-in functions are declared in `std.sx` with the `#builtin` suffix, which te - `sqrt(x: $T) -> T` — square root (maps to LLVM intrinsic) ### Memory -- `alloc(size: s32) -> string` — allocate `size` bytes of memory, returned as a string slice -- `size_of($T: Type) -> s32` — size of type `T` in bytes +- `alloc(size: s64) -> string` — allocate `size` bytes of memory, returned as a string slice +- `size_of($T: Type) -> s64` — size of type `T` in bytes ### Type Introspection - `type_of(val: $T) -> Type` — returns the runtime type tag of a value - `type_name($T: Type) -> string` — returns the name of type `T` as a string (e.g., `"Point"`) -- `field_count($T: Type) -> s32` — returns the number of fields (struct), variants (enum), or elements (vector) in type `T` -- `field_name($T: Type, idx: s32) -> string` — returns the name of the `idx`-th field (struct) or variant (enum) of type `T` -- `field_value(s: $T, idx: s32) -> Any` — returns the `idx`-th field (struct) or element (vector) of `s`, boxed as `Any` +- `field_count($T: Type) -> s64` — returns the number of fields (struct), variants (enum), or elements (vector) in type `T` +- `field_name($T: Type, idx: s64) -> string` — returns the name of the `idx`-th field (struct) or variant (enum) of type `T` +- `field_value(s: $T, idx: s64) -> Any` — returns the `idx`-th field (struct) or element (vector) of `s`, boxed as `Any` ### Type Conversion - `cast(Type) expr` — prefix operator that converts `expr` to `Type`. Examples: `cast(s32) 3.14`, `cast(f64) n`. When `Type` is a runtime `Type` value inside a type-category match arm, the compiler generates a dispatch switch over all types in the category, monomorphizing the callee for each concrete type. diff --git a/src/codegen.zig b/src/codegen.zig index 9b37b18..ddc0893 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -58,6 +58,8 @@ pub const CodeGen = struct { namespaces: std.StringHashMap(void), // Functions declared with #builtin (only available when imported) builtin_functions: std.StringHashMap(void), + // Function signatures: maps function name to display signature (e.g. "test :: () -> s32") + fn_signatures: std.StringHashMap([]const u8), // Active namespace during body generation of imported modules current_namespace: ?[]const u8 = null, // Diagnostics list (optional, for structured error reporting) @@ -102,6 +104,7 @@ pub const CodeGen = struct { vector_cat, array_cat, slice_cat, + pointer_cat, }; const AnyTypeEntry = struct { @@ -181,6 +184,7 @@ pub const CodeGen = struct { .generic_struct_templates = std.StringHashMap(GenericStructTemplate).init(allocator), .namespaces = std.StringHashMap(void).init(allocator), .builtin_functions = std.StringHashMap(void).init(allocator), + .fn_signatures = std.StringHashMap([]const u8).init(allocator), .variadic_functions = std.StringHashMap(VariadicInfo).init(allocator), .fn_param_types = std.StringHashMap([]const Type).init(allocator), .any_type_id_map = std.StringHashMap(u64).init(allocator), @@ -201,6 +205,7 @@ pub const CodeGen = struct { self.generic_struct_templates.deinit(); self.namespaces.deinit(); self.builtin_functions.deinit(); + self.fn_signatures.deinit(); self.variadic_functions.deinit(); self.any_type_id_map.deinit(); self.any_type_entries.deinit(); @@ -229,7 +234,7 @@ pub const CodeGen = struct { .void_type => c.LLVMVoidTypeInContext(self.context), .boolean => c.LLVMInt1TypeInContext(self.context), .string_type, .slice_type => self.getStringStructType(), // slices use same {ptr, i32} layout - .enum_type => c.LLVMInt32TypeInContext(self.context), + .enum_type => c.LLVMInt64TypeInContext(self.context), .struct_type => |name| if (self.struct_types.get(name)) |info| info.llvm_type else unreachable, .union_type => |name| if (self.union_types.get(name)) |info| info.llvm_type else unreachable, .array_type => |info| { @@ -249,7 +254,7 @@ pub const CodeGen = struct { fn getAnyStructType(self: *CodeGen) c.LLVMTypeRef { if (self.any_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ - c.LLVMInt32TypeInContext(self.context), // type tag + c.LLVMInt64TypeInContext(self.context), // type tag c.LLVMInt64TypeInContext(self.context), // value (fits all primitives) }; self.any_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); @@ -285,6 +290,7 @@ pub const CodeGen = struct { .vector_type => .vector_cat, .array_type => .array_cat, .slice_type => .slice_cat, + .pointer_type, .many_pointer_type => .pointer_cat, else => .struct_cat, // fallback }; try self.any_type_entries.put(name, .{ @@ -320,6 +326,8 @@ pub const CodeGen = struct { .vector_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ info.length, info.element_name }), sx_type), .array_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ info.length, info.element_name }), sx_type), .slice_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[]{s}", .{info.element_name}), sx_type), + .pointer_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "*{s}", .{info.pointee_name}), sx_type), + .many_pointer_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[*]{s}", .{info.element_name}), sx_type), else => {}, } } @@ -329,7 +337,6 @@ pub const CodeGen = struct { /// Complex values (strings, structs, unions) are stored via pointer (alloca + ptr-to-int). fn buildAnyValue(self: *CodeGen, val: c.LLVMValueRef, ty: Type) !c.LLVMValueRef { const any_ty = self.getAnyStructType(); - const i32_ty = c.LLVMInt32TypeInContext(self.context); const i64_ty = c.LLVMInt64TypeInContext(self.context); const undef = c.LLVMGetUndef(any_ty); @@ -353,7 +360,7 @@ pub const CodeGen = struct { .meta_type => ANY_TAG_TYPE, else => ANY_TAG_S32, }; - const tag_val = c.LLVMConstInt(i32_ty, tag, 0); + const tag_val = c.LLVMConstInt(i64_ty, tag, 0); const with_tag = c.LLVMBuildInsertValue(self.builder, undef, tag_val, 0, "any_tag"); // Convert value to i64 @@ -413,9 +420,12 @@ pub const CodeGen = struct { break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_slice"); }, .pointer_type, .many_pointer_type => c.LLVMBuildPtrToInt(self.builder, val, i64_ty, "any_ptr"), - .meta_type => blk: { - // Meta type is a pointer (global string) — convert via ptrtoint - break :blk c.LLVMBuildPtrToInt(self.builder, val, i64_ty, "any_type"); + .meta_type => |mt| blk: { + // Meta type: wrap raw char ptr in string slice {ptr, len} for extraction + const str_slice = self.buildStringSlice(val, mt.name.len); + const alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_type_tmp"); + _ = c.LLVMBuildStore(self.builder, str_slice, alloca); + break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_type"); }, else => c.LLVMBuildSExt(self.builder, val, i64_ty, "any_val"), }; @@ -426,18 +436,18 @@ pub const CodeGen = struct { if (self.string_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ c.LLVMPointerTypeInContext(self.context, 0), // ptr - c.LLVMInt32TypeInContext(self.context), // len + c.LLVMInt64TypeInContext(self.context), // len }; self.string_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); return self.string_struct_type.?; } /// Build a string slice {ptr, len} from a raw pointer and a constant length. - fn buildStringSlice(self: *CodeGen, ptr: c.LLVMValueRef, len: u32) c.LLVMValueRef { + fn buildStringSlice(self: *CodeGen, ptr: c.LLVMValueRef, len: u64) c.LLVMValueRef { const str_ty = self.getStringStructType(); const undef = c.LLVMGetUndef(str_ty); const with_ptr = c.LLVMBuildInsertValue(self.builder, undef, ptr, 0, "str_ptr"); - const len_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), len, 0); + const len_val = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), len, 0); return c.LLVMBuildInsertValue(self.builder, with_ptr, len_val, 1, "str_slice"); } @@ -525,6 +535,7 @@ pub const CodeGen = struct { } else { try self.registerFnDecl(fd); } + try self.fn_signatures.put(fd.name, self.buildFnSignature(fd)); }, .enum_decl => |ed| { try self.enum_types.put(ed.name, ed.variants); @@ -560,6 +571,14 @@ pub const CodeGen = struct { } else if (result_ty.isUnion()) { try self.type_aliases.put(cd.name, result_ty.union_type); } + } else if (self.builtin_functions.contains(cn)) { + // Builtin type function (e.g., Vector(4, f32), Array(5, s32)) + if (self.resolveBuiltinType(cn, cd.value.data.call.args)) |result_ty| { + const display = try result_ty.displayName(self.allocator); + try self.type_aliases.put(cd.name, display); + } else { + try self.registerTopLevelConstant(cd); + } } else { try self.registerTopLevelConstant(cd); } @@ -727,6 +746,8 @@ pub const CodeGen = struct { break :blk self.buildStringSlice(ptr, @intCast(v.len)); }, .void_val => c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), + .pointer_val => c.LLVMConstNull(c.LLVMPointerTypeInContext(self.context, 0)), + .null_val => c.LLVMConstNull(c.LLVMPointerTypeInContext(self.context, 0)), .struct_val, .array_val, .type_val, .function_val => unreachable, }; } @@ -1084,10 +1105,10 @@ pub const CodeGen = struct { const name_z = try self.allocator.dupeZ(u8, mangled_name); const union_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i64_ty = c.LLVMInt64TypeInContext(self.context); const i8_ty = c.LLVMInt8TypeInContext(self.context); const payload_array_ty = c.LLVMArrayType2(i8_ty, max_payload_size); - var fields = [2]c.LLVMTypeRef{ i32_ty, payload_array_ty }; + var fields = [2]c.LLVMTypeRef{ i64_ty, payload_array_ty }; c.LLVMStructSetBody(union_ty, &fields, 2, 0); try self.union_types.put(mangled_name, .{ @@ -1273,8 +1294,8 @@ pub const CodeGen = struct { // For function calls, look up the registered function's return type if (expr.data == .call) { if (self.resolveCalleeName(expr.data.call)) |callee_name| { - const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(32); - const callee_fn = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr) orelse return Type.s(32); + const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(64); + const callee_fn = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr) orelse return Type.s(64); const fn_type = c.LLVMGlobalGetValueType(callee_fn); const ret_llvm = c.LLVMGetReturnType(fn_type); return self.llvmTypeToSxType(ret_llvm); @@ -1315,7 +1336,7 @@ pub const CodeGen = struct { const elem_llvm = c.LLVMGetElementType(llvm_ty); const length: u32 = @intCast(c.LLVMGetArrayLength2(llvm_ty)); const elem_ty = self.llvmTypeToSxType(elem_llvm); - const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(32); + const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(64); return .{ .array_type = .{ .element_name = elem_name, .length = length } }; } // Check for vector types @@ -1323,10 +1344,10 @@ pub const CodeGen = struct { const elem_llvm = c.LLVMGetElementType(llvm_ty); const length = c.LLVMGetVectorSize(llvm_ty); const elem_ty = self.llvmTypeToSxType(elem_llvm); - const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(32); + const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(64); return .{ .vector_type = .{ .element_name = elem_name, .length = length } }; } - return Type.s(32); + return Type.s(64); } fn registerComptimeGlobal(self: *CodeGen, name: []const u8, expr: *Node, type_override: ?Type) !void { @@ -1673,7 +1694,7 @@ pub const CodeGen = struct { if (vd.value) |val| { const meta_name = self.asTypeName(val); if (meta_name) |raw_name| { - const type_name = self.resolveDisplayName(raw_name); + const type_name = try self.allocator.dupeZ(u8, raw_name); const name_z = try self.allocator.dupeZ(u8, vd.name); const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0); const alloca = c.LLVMBuildAlloca(self.builder, ptr_ty, name_z.ptr); @@ -1685,7 +1706,7 @@ pub const CodeGen = struct { } } - var sx_ty: Type = Type.s(32); + var sx_ty: Type = Type.s(64); if (vd.type_annotation) |ta| { sx_ty = self.resolveType(ta); @@ -1928,7 +1949,7 @@ pub const CodeGen = struct { return null; } - var sx_ty: Type = Type.s(32); + var sx_ty: Type = Type.s(64); if (cd.type_annotation) |ta| { sx_ty = self.resolveType(ta); @@ -1989,14 +2010,22 @@ pub const CodeGen = struct { return self.emitErrorFmt("undefined variable '{s}'", .{name}); }; - // Meta type reassignment: x = Vec4 or x = f64 + // Meta type reassignment: x = Vec4, x = f64, x = test if (entry.ty == .meta_type and asgn.op == .assign) { - if (self.asTypeName(asgn.value)) |raw_name| { - const type_name = self.resolveDisplayName(raw_name); + const raw_name = self.asTypeName(asgn.value) orelse blk: { + // Also accept function names as meta_type values (use signature) + if (asgn.value.data == .identifier) { + const fn_name = asgn.value.data.identifier.name; + if (self.fn_signatures.get(fn_name)) |sig| break :blk sig; + } + break :blk @as(?[]const u8, null); + }; + if (raw_name) |rn| { + const type_name = try self.allocator.dupeZ(u8, rn); const str_val = c.LLVMBuildGlobalStringPtr(self.builder, type_name.ptr, "type_name"); _ = c.LLVMBuildStore(self.builder, str_val, entry.ptr); if (self.named_values.getPtr(name)) |entry_ptr| { - entry_ptr.ty = .{ .meta_type = .{ .name = raw_name } }; + entry_ptr.ty = .{ .meta_type = .{ .name = rn } }; } return null; } @@ -2258,8 +2287,8 @@ pub const CodeGen = struct { self.current_span = node.span; switch (node.data) { .int_literal => |lit| { - const i32_type = c.LLVMInt32TypeInContext(self.context); - return c.LLVMConstInt(i32_type, @bitCast(@as(i64, lit.value)), 0); + const i64_type = c.LLVMInt64TypeInContext(self.context); + return c.LLVMConstInt(i64_type, @bitCast(@as(i64, lit.value)), 0); }, .float_literal => |lit| { const f32_type = c.LLVMFloatTypeInContext(self.context); @@ -2617,13 +2646,13 @@ pub const CodeGen = struct { } } - // Union LLVM type: { i32, [max_payload_size x i8] } + // Union LLVM type: { i64, [max_payload_size x i8] } const name_z = try self.allocator.dupeZ(u8, ud.name); const union_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i64_ty = c.LLVMInt64TypeInContext(self.context); const i8_ty = c.LLVMInt8TypeInContext(self.context); const payload_array_ty = c.LLVMArrayType2(i8_ty, max_payload_size); - var fields = [2]c.LLVMTypeRef{ i32_ty, payload_array_ty }; + var fields = [2]c.LLVMTypeRef{ i64_ty, payload_array_ty }; c.LLVMStructSetBody(union_ty, &fields, 2, 0); try self.union_types.put(ud.name, .{ @@ -2654,11 +2683,11 @@ pub const CodeGen = struct { // Alloca union const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_tmp"); - const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i64_ty = c.LLVMInt64TypeInContext(self.context); // Store tag (field 0) const tag_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, 0, "tag"); - _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_ty, idx, 0), tag_gep); + _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i64_ty, idx, 0), tag_gep); // Store payload (field 1) if not void if (ul.payload) |payload_node| { @@ -2790,14 +2819,14 @@ pub const CodeGen = struct { } // Build slice {ptr, len} - const i32_ty = c.LLVMInt32TypeInContext(self.context); - const zero = c.LLVMConstInt(i32_ty, 0, 0); + const i64_ty = c.LLVMInt64TypeInContext(self.context); + const zero = c.LLVMConstInt(i64_ty, 0, 0); var gep_indices = [_]c.LLVMValueRef{ zero, zero }; const elem_ptr = c.LLVMBuildGEP2(self.builder, llvm_arr_ty, arr_alloca, &gep_indices, 2, "slice_data"); const slice_llvm_ty = self.getStringStructType(); var slice_val = c.LLVMGetUndef(slice_llvm_ty); slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, elem_ptr, 0, "slice_ptr"); - const len_val = c.LLVMConstInt(i32_ty, n, 0); + const len_val = c.LLVMConstInt(i64_ty, n, 0); slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, len_val, 1, "slice_len"); return slice_val; } @@ -2942,15 +2971,15 @@ pub const CodeGen = struct { break :blk try self.genExpr(node); }; // GEP to get pointer to first element - const i32_ty = c.LLVMInt32TypeInContext(self.context); - const zero = c.LLVMConstInt(i32_ty, 0, 0); + const i64_ty = c.LLVMInt64TypeInContext(self.context); + const zero = c.LLVMConstInt(i64_ty, 0, 0); var indices = [_]c.LLVMValueRef{ zero, zero }; const elem_ptr = c.LLVMBuildGEP2(self.builder, self.typeToLLVM(src_ty), arr_alloca, &indices, 2, "arr_data"); // Build slice struct {ptr, len} const slice_ty = self.getStringStructType(); var slice_val = c.LLVMGetUndef(slice_ty); slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, elem_ptr, 0, "slice_ptr"); - const len_val = c.LLVMConstInt(i32_ty, arr_info.length, 0); + const len_val = c.LLVMConstInt(i64_ty, arr_info.length, 0); slice_val = c.LLVMBuildInsertValue(self.builder, slice_val, len_val, 1, "slice_len"); return slice_val; } @@ -3022,7 +3051,7 @@ pub const CodeGen = struct { } } if (target_ty.isEnum()) { - return c.LLVMBuildTrunc(self.builder, i64_val, c.LLVMInt32TypeInContext(self.context), "any_to_enum"); + return i64_val; } if (target_ty.isUnion()) { const uname = target_ty.union_type; @@ -3031,6 +3060,9 @@ pub const CodeGen = struct { return c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_union"); } } + if (target_ty.isPointer() or target_ty.isManyPointer()) { + return c.LLVMBuildIntToPtr(self.builder, i64_val, c.LLVMPointerTypeInContext(self.context, 0), "any_to_ptr"); + } return i64_val; } @@ -3061,6 +3093,15 @@ pub const CodeGen = struct { } } + // Pointer → int: PtrToInt + if ((src_ty.isPointer() or src_ty.isManyPointer()) and target_ty.isInt()) { + const as_i64 = c.LLVMBuildPtrToInt(self.builder, val, c.LLVMInt64TypeInContext(self.context), "ptrtoint"); + if (target_ty.bitWidth() < 64) { + return c.LLVMBuildTrunc(self.builder, as_i64, target_llvm, "ptr_trunc"); + } + return as_i64; + } + // Union → int: extract the tag field (index 0) if (src_ty.isUnion() and target_ty.isInt()) { const uname = src_ty.union_type; @@ -3149,19 +3190,19 @@ pub const CodeGen = struct { if (call_node.args.len != 1) return self.emitError("size_of expects exactly 1 argument"); const ty = self.resolveType(call_node.args[0]); if (std.meta.eql(ty, Type.void_type)) { - return c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + return c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0); } const llvm_ty = self.typeToLLVM(ty); const data_layout = c.LLVMGetModuleDataLayout(self.module); const size = c.LLVMStoreSizeOfType(data_layout, llvm_ty); - return c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), size, 0); + return c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), size, 0); } fn genTypeOf(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("type_of expects exactly 1 argument"); const arg = call_node.args[0]; const arg_ty = self.inferType(arg); - const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i64_ty = c.LLVMInt64TypeInContext(self.context); // For Any values: extract the runtime tag (field 0) if (arg_ty.isAny()) { @@ -3184,7 +3225,7 @@ pub const CodeGen = struct { .meta_type => ANY_TAG_TYPE, else => ANY_TAG_S32, }; - return c.LLVMConstInt(i32_ty, tag, 0); + return c.LLVMConstInt(i64_ty, tag, 0); } fn genTypeName(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { @@ -3197,27 +3238,27 @@ pub const CodeGen = struct { fn genFieldCount(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("field_count expects exactly 1 argument"); const ty = self.resolveType(call_node.args[0]); - const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i64_ty = c.LLVMInt64TypeInContext(self.context); if (ty.isStruct()) { const info = self.struct_types.get(ty.struct_type) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{ty.struct_type}); - return c.LLVMConstInt(i32_ty, info.field_names.len, 0); + return c.LLVMConstInt(i64_ty, info.field_names.len, 0); } if (ty.isEnum()) { const variants = self.enum_types.get(ty.enum_type) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type}); - return c.LLVMConstInt(i32_ty, variants.len, 0); + return c.LLVMConstInt(i64_ty, variants.len, 0); } if (ty.isVector()) { - return c.LLVMConstInt(i32_ty, ty.vector_type.length, 0); + return c.LLVMConstInt(i64_ty, ty.vector_type.length, 0); } if (ty.isUnion()) { const info = self.union_types.get(ty.union_type) orelse return self.emitErrorFmt("unknown union type '{s}'", .{ty.union_type}); - return c.LLVMConstInt(i32_ty, info.variant_names.len, 0); + return c.LLVMConstInt(i64_ty, info.variant_names.len, 0); } if (ty.isArray()) { - return c.LLVMConstInt(i32_ty, ty.array_type.length, 0); + return c.LLVMConstInt(i64_ty, ty.array_type.length, 0); } return self.emitError("field_count requires a struct, enum, vector, union, or array type"); } @@ -3294,7 +3335,7 @@ pub const CodeGen = struct { // Read tag (field 0) const tag_ptr = c.LLVMBuildStructGEP2(self.builder, uinfo.llvm_type, union_alloca, 0, "fv_tag_ptr"); - const tag_val = c.LLVMBuildLoad2(self.builder, c.LLVMInt32TypeInContext(self.context), tag_ptr, "fv_tag"); + const tag_val = c.LLVMBuildLoad2(self.builder, c.LLVMInt64TypeInContext(self.context), tag_ptr, "fv_tag"); const payload_ptr = c.LLVMBuildStructGEP2(self.builder, uinfo.llvm_type, union_alloca, 1, "fv_payload_ptr"); const n = uinfo.variant_names.len; @@ -3309,13 +3350,13 @@ pub const CodeGen = struct { for (uinfo.variant_types, 0..) |vty, vi| { const case_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_ucase"); - c.LLVMAddCase(sw, c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(vi), 0), case_bb); + c.LLVMAddCase(sw, c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), @intCast(vi), 0), case_bb); c.LLVMPositionBuilderAtEnd(self.builder, case_bb); const any_val = if (vty == .void_type) blk: { // Void variant: return Any with void tag const undef = c.LLVMGetUndef(any_ty); - const void_tag = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), ANY_TAG_VOID, 0); + const void_tag = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), ANY_TAG_VOID, 0); const with_tag = c.LLVMBuildInsertValue(self.builder, undef, void_tag, 0, "void_tag"); const zero_val = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0); break :blk c.LLVMBuildInsertValue(self.builder, with_tag, zero_val, 1, "void_any"); @@ -3403,7 +3444,7 @@ pub const CodeGen = struct { for (0..n) |i| { const case_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_case"); - const case_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), i, 0); + const case_val = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), i, 0); c.LLVMAddCase(sw, case_val, case_bb); c.LLVMPositionBuilderAtEnd(self.builder, case_bb); @@ -3444,11 +3485,9 @@ pub const CodeGen = struct { const builtins = self.builtins orelse return self.emitError("builtins not available (missing #builtin import)"); const size_val = try self.genExpr(args[0]); const i64_type = c.LLVMInt64TypeInContext(self.context); - // Extend size to i64 for calloc - const size_i64 = c.LLVMBuildSExt(self.builder, size_val, i64_type, "size64"); // calloc(size + 1, 1) — extra byte for null terminator const one_i64 = c.LLVMConstInt(i64_type, 1, 0); - const size_plus_one = c.LLVMBuildAdd(self.builder, size_i64, one_i64, "szp1"); + const size_plus_one = c.LLVMBuildAdd(self.builder, size_val, one_i64, "szp1"); const calloc_fn = builtins.calloc_fn; const calloc_ty = c.LLVMGlobalGetValueType(calloc_fn); var calloc_args = [_]c.LLVMValueRef{ size_plus_one, one_i64 }; @@ -3549,7 +3588,7 @@ pub const CodeGen = struct { } if (entry.ty.isArray()) { if (std.mem.eql(u8, fa.field, "len")) { - return c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), entry.ty.array_type.length, 0); + return c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), entry.ty.array_type.length, 0); } return self.emitErrorFmt("no field '{s}' on array (available: .len)", .{fa.field}); } @@ -3623,15 +3662,14 @@ pub const CodeGen = struct { } } if (obj_ty == .string_type) { - // String indexing: extract ptr from slice, GEP + load i8 + zext to i32 + // String indexing: extract ptr from slice, GEP + load u8 const str_val = try self.genExpr(ie.object); const ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str_ptr"); const idx = try self.genExpr(ie.index); const i8_type = c.LLVMInt8TypeInContext(self.context); var gep_indices = [_]c.LLVMValueRef{idx}; const gep = c.LLVMBuildGEP2(self.builder, i8_type, ptr, &gep_indices, 1, "stridx"); - const byte = c.LLVMBuildLoad2(self.builder, i8_type, gep, "byte"); - return c.LLVMBuildZExt(self.builder, byte, c.LLVMInt32TypeInContext(self.context), "char"); + return c.LLVMBuildLoad2(self.builder, i8_type, gep, "byte"); } if (obj_ty.isSlice()) { // Slice indexing: extract ptr, GEP with element type, load @@ -3672,8 +3710,8 @@ pub const CodeGen = struct { fn genSliceExpr(self: *CodeGen, se: ast.SliceExpr) !c.LLVMValueRef { const obj_ty = self.inferType(se.object); - const i32_ty = c.LLVMInt32TypeInContext(self.context); - const zero = c.LLVMConstInt(i32_ty, 0, 0); + const i64_ty = c.LLVMInt64TypeInContext(self.context); + const zero = c.LLVMConstInt(i64_ty, 0, 0); const slice_struct_ty = self.getStringStructType(); // Resolve start (default: 0) @@ -3682,7 +3720,7 @@ pub const CodeGen = struct { if (obj_ty.isArray()) { const arr_info = obj_ty.array_type; // Resolve end (default: array length) - const end_val = if (se.end) |e| try self.genExpr(e) else c.LLVMConstInt(i32_ty, arr_info.length, 0); + const end_val = if (se.end) |e| try self.genExpr(e) else c.LLVMConstInt(i64_ty, arr_info.length, 0); // Get array alloca const arr_ptr = blk: { if (se.object.data == .identifier) { @@ -4042,7 +4080,7 @@ pub const CodeGen = struct { } } // Pack variadic args into a slice {ptr, len} - const elem_ty = Type.fromName(vi.element_type_name) orelse Type.s(32); + const elem_ty = Type.fromName(vi.element_type_name) orelse Type.s(64); const elem_llvm_ty = self.typeToLLVM(elem_ty); const var_arg_count = if (call_node.args.len > fixed_count) call_node.args.len - fixed_count else 0; @@ -4056,10 +4094,10 @@ pub const CodeGen = struct { if (spread_operand.data == .identifier) { if (self.named_values.get(spread_operand.data.identifier.name)) |entry| { const arr_llvm_ty = self.typeToLLVM(spread_ty); - const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + const zero = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0); var ptr_indices = [_]c.LLVMValueRef{ zero, zero }; const arr_ptr = c.LLVMBuildGEP2(self.builder, arr_llvm_ty, entry.ptr, &ptr_indices, 2, "spread_ptr"); - const len_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), arr_info.length, 0); + const len_val = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), arr_info.length, 0); const slice_val = self.buildStringSliceRT(arr_ptr, len_val); try arg_vals.append(self.allocator, slice_val); } else { @@ -4094,16 +4132,16 @@ pub const CodeGen = struct { _ = c.LLVMBuildStore(self.builder, arg_val, gep); } // Build slice: {ptr, len} - const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + const zero = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0); var ptr_indices = [_]c.LLVMValueRef{ zero, zero }; const arr_ptr = c.LLVMBuildGEP2(self.builder, arr_ty, arr_alloca, &ptr_indices, 2, "varargs_ptr"); - const len_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(var_arg_count), 0); + const len_val = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), @intCast(var_arg_count), 0); const slice_val = self.buildStringSliceRT(arr_ptr, len_val); try arg_vals.append(self.allocator, slice_val); } else { // Zero variadic args: pass empty slice {null, 0} const null_ptr = c.LLVMConstNull(c.LLVMPointerTypeInContext(self.context, 0)); - const zero_len = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + const zero_len = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0); const slice_val = self.buildStringSliceRT(null_ptr, zero_len); try arg_vals.append(self.allocator, slice_val); } @@ -4466,7 +4504,7 @@ pub const CodeGen = struct { // Create case BB const case_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "dispatch_case"); - c.LLVMAddCase(sw, c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), tag, 0), case_bb); + c.LLVMAddCase(sw, c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), tag, 0), case_bb); c.LLVMPositionBuilderAtEnd(self.builder, case_bb); // Convert Any payload to the concrete type @@ -4594,7 +4632,7 @@ pub const CodeGen = struct { const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_struct_ptr"); break :blk c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_struct"); }, - .enum_type => c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMInt32TypeInContext(self.context), "any_to_enum"), + .enum_type => any_i64, .union_type => |uname| blk: { const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union '{s}'", .{uname}); const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_union_ptr"); @@ -4609,7 +4647,8 @@ pub const CodeGen = struct { const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_slice_ptr"); break :blk c.LLVMBuildLoad2(self.builder, self.getStringStructType(), ptr, "any_to_slice"); }, - else => c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMInt32TypeInContext(self.context), "any_to_default"), + .pointer_type, .many_pointer_type => c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_ptr"), + else => any_i64, }; } @@ -4869,19 +4908,19 @@ pub const CodeGen = struct { fn genForExpr(self: *CodeGen, for_expr: ast.ForExpr) !c.LLVMValueRef { const function = self.current_function; - const i32_type = c.LLVMInt32TypeInContext(self.context); + const i64_type = c.LLVMInt64TypeInContext(self.context); // Determine iterable type and get length + element access info const iter_ty = self.inferType(for_expr.iterable); var len_val: c.LLVMValueRef = undefined; - var elem_ty: Type = Type.s(32); + var elem_ty: Type = Type.s(64); var iter_ptr: c.LLVMValueRef = undefined; // pointer to data var is_slice = false; if (iter_ty.isSlice()) { is_slice = true; const info = iter_ty.slice_type; - elem_ty = Type.fromName(info.element_name) orelse Type.s(32); + elem_ty = Type.fromName(info.element_name) orelse Type.s(64); // Load slice value from alloca if (for_expr.iterable.data == .identifier) { if (self.named_values.get(for_expr.iterable.data.identifier.name)) |entry| { @@ -4892,8 +4931,8 @@ pub const CodeGen = struct { } else return self.emitError("for: slice iterable must be a variable"); } else if (iter_ty.isArray()) { const info = iter_ty.array_type; - elem_ty = Type.fromName(info.element_name) orelse Type.s(32); - len_val = c.LLVMConstInt(i32_type, info.length, 0); + elem_ty = Type.fromName(info.element_name) orelse Type.s(64); + len_val = c.LLVMConstInt(i64_type, info.length, 0); // Get pointer to array if (for_expr.iterable.data == .identifier) { if (self.named_values.get(for_expr.iterable.data.identifier.name)) |entry| { @@ -4906,15 +4945,15 @@ pub const CodeGen = struct { const elem_llvm_ty = self.typeToLLVM(elem_ty); - // Allocate it_index (s32) and it (element type) - const idx_alloca = c.LLVMBuildAlloca(self.builder, i32_type, "it_index"); - _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_type, 0, 0), idx_alloca); + // Allocate it_index (s64) and it (element type) + const idx_alloca = c.LLVMBuildAlloca(self.builder, i64_type, "it_index"); + _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i64_type, 0, 0), idx_alloca); const it_alloca = c.LLVMBuildAlloca(self.builder, elem_llvm_ty, "it"); // Push scope and bind it, it_index try self.pushScope(); try self.named_values.put("it", .{ .ptr = it_alloca, .ty = elem_ty }); - try self.named_values.put("it_index", .{ .ptr = idx_alloca, .ty = Type.s(32) }); + try self.named_values.put("it_index", .{ .ptr = idx_alloca, .ty = Type.s(64) }); // Create basic blocks const cond_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "for.cond"); @@ -4925,13 +4964,13 @@ pub const CodeGen = struct { // Condition: it_index < len c.LLVMPositionBuilderAtEnd(self.builder, cond_bb); - const cur_idx = c.LLVMBuildLoad2(self.builder, i32_type, idx_alloca, "cur_idx"); + const cur_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "cur_idx"); const cond_val = c.LLVMBuildICmp(self.builder, c.LLVMIntSLT, cur_idx, len_val, "for_cond"); _ = c.LLVMBuildCondBr(self.builder, cond_val, body_bb, after_bb); // Body: load it = iterable[it_index], then execute body c.LLVMPositionBuilderAtEnd(self.builder, body_bb); - const body_idx = c.LLVMBuildLoad2(self.builder, i32_type, idx_alloca, "body_idx"); + const body_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "body_idx"); if (is_slice) { // Slice: GEP through data pointer @@ -4942,7 +4981,7 @@ pub const CodeGen = struct { } else { // Array: GEP with [0, idx] const arr_llvm_ty = self.typeToLLVM(iter_ty); - const zero = c.LLVMConstInt(i32_type, 0, 0); + const zero = c.LLVMConstInt(i64_type, 0, 0); var indices = [_]c.LLVMValueRef{ zero, body_idx }; const gep = c.LLVMBuildGEP2(self.builder, arr_llvm_ty, iter_ptr, &indices, 2, "for_elem"); const elem_val = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "it_val"); @@ -4963,8 +5002,8 @@ pub const CodeGen = struct { // Increment it_index const current_bb = c.LLVMGetInsertBlock(self.builder); if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { - const inc_idx = c.LLVMBuildLoad2(self.builder, i32_type, idx_alloca, "inc_idx"); - const next_idx = c.LLVMBuildAdd(self.builder, inc_idx, c.LLVMConstInt(i32_type, 1, 0), "next_idx"); + const inc_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "inc_idx"); + const next_idx = c.LLVMBuildAdd(self.builder, inc_idx, c.LLVMConstInt(i64_type, 1, 0), "next_idx"); _ = c.LLVMBuildStore(self.builder, next_idx, idx_alloca); _ = c.LLVMBuildBr(self.builder, cond_bb); } @@ -4977,14 +5016,14 @@ pub const CodeGen = struct { } fn genEnumLiteral(self: *CodeGen, variant_name: []const u8, enum_type_name: []const u8) c.LLVMValueRef { - const i32_type = c.LLVMInt32TypeInContext(self.context); - const variants = self.enum_types.get(enum_type_name) orelse return c.LLVMConstInt(i32_type, 0, 0); + const i64_type = c.LLVMInt64TypeInContext(self.context); + const variants = self.enum_types.get(enum_type_name) orelse return c.LLVMConstInt(i64_type, 0, 0); for (variants, 0..) |v, i| { if (std.mem.eql(u8, v, variant_name)) { - return c.LLVMConstInt(i32_type, @intCast(i), 0); + return c.LLVMConstInt(i64_type, @intCast(i), 0); } } - return c.LLVMConstInt(i32_type, 0, 0); + return c.LLVMConstInt(i64_type, 0, 0); } fn lookupVariantIndex(variants: ?[]const []const u8, name: []const u8) u64 { @@ -5013,7 +5052,7 @@ pub const CodeGen = struct { const entry = self.named_values.get(match.subject.data.identifier.name).?; const info = self.union_types.get(union_name.?).?; const tag_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, entry.ptr, 0, "tag"); - break :blk c.LLVMBuildLoad2(self.builder, c.LLVMInt32TypeInContext(self.context), tag_gep, "tag_val"); + break :blk c.LLVMBuildLoad2(self.builder, c.LLVMInt64TypeInContext(self.context), tag_gep, "tag_val"); } else try self.genExpr(match.subject); const variants: ?[]const []const u8 = if (union_name) |un| @@ -5024,7 +5063,7 @@ pub const CodeGen = struct { null; const function = self.current_function; - const i32_type = c.LLVMInt32TypeInContext(self.context); + const i64_type = c.LLVMInt64TypeInContext(self.context); const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "match_end"); // Create case basic blocks @@ -5052,19 +5091,19 @@ pub const CodeGen = struct { const pat = arm.pattern orelse continue; // skip else arm if (pat.data == .enum_literal) { const idx = lookupVariantIndex(variants, pat.data.enum_literal.name); - const case_val = c.LLVMConstInt(i32_type, idx, 0); + const case_val = c.LLVMConstInt(i64_type, idx, 0); c.LLVMAddCase(sw, case_val, case_bbs.items[i]); } else if (pat.data == .type_expr) { // Type-match: resolve type name to Any tag value(s) const tag_values = try self.resolveTypeMatchTags(pat.data.type_expr.name); for (tag_values) |tag| { - c.LLVMAddCase(sw, c.LLVMConstInt(i32_type, tag, 0), case_bbs.items[i]); + c.LLVMAddCase(sw, c.LLVMConstInt(i64_type, tag, 0), case_bbs.items[i]); } } else if (pat.data == .identifier) { // Named type (struct/enum/union name) or category (int/float) const tag_values = try self.resolveTypeMatchTags(pat.data.identifier.name); for (tag_values) |tag| { - c.LLVMAddCase(sw, c.LLVMConstInt(i32_type, tag, 0), case_bbs.items[i]); + c.LLVMAddCase(sw, c.LLVMConstInt(i64_type, tag, 0), case_bbs.items[i]); } } } @@ -5179,6 +5218,8 @@ pub const CodeGen = struct { .array_cat else if (std.mem.eql(u8, name, "slice")) .slice_cat + else if (std.mem.eql(u8, name, "pointer")) + .pointer_cat else null; if (category) |cat| { @@ -5208,7 +5249,7 @@ pub const CodeGen = struct { ANY_TAG_F64 else if (std.mem.eql(u8, name, "string")) ANY_TAG_STRING - else if (std.mem.eql(u8, name, "Type")) + else if (std.mem.eql(u8, name, "Type") or std.mem.eql(u8, name, "type")) ANY_TAG_TYPE else if (std.mem.eql(u8, name, "void")) ANY_TAG_VOID @@ -5303,7 +5344,9 @@ pub const CodeGen = struct { const val = try self.genExpr(args[0]); // Extract ptr and len from string slice const ptr = c.LLVMBuildExtractValue(self.builder, val, 0, "str_ptr"); - const len = c.LLVMBuildExtractValue(self.builder, val, 1, "str_len"); + const len_i64 = c.LLVMBuildExtractValue(self.builder, val, 1, "str_len"); + // printf %.*s precision is C int (i32) — truncate from i64 + const len = c.LLVMBuildTrunc(self.builder, len_i64, c.LLVMInt32TypeInContext(self.context), "len_trunc"); const fmt = c.LLVMBuildGlobalStringPtr(self.builder, "%.*s", "write_fmt"); const printf_fn = builtins.printf_fn; const fn_type = c.LLVMGlobalGetValueType(printf_fn); @@ -5322,7 +5365,7 @@ pub const CodeGen = struct { /// Helper: build a constant string slice as a global constant (no builder needed). fn buildConstStrGlobal(self: *CodeGen, s: []const u8) c.LLVMValueRef { const sz = self.allocator.dupeZ(u8, s) catch unreachable; - const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i64_ty = c.LLVMInt64TypeInContext(self.context); const i8_ty = c.LLVMInt8TypeInContext(self.context); // Create a global string constant const str_const = c.LLVMConstStringInContext(self.context, sz.ptr, @intCast(s.len), 0); @@ -5335,10 +5378,10 @@ pub const CodeGen = struct { c.LLVMSetGlobalConstant(global, 1); c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); } - // Build constant struct {ptr, i32} + // Build constant struct {ptr, i64} var fields = [_]c.LLVMValueRef{ c.LLVMConstBitCast(global.?, c.LLVMPointerTypeInContext(self.context, 0)), - c.LLVMConstInt(i32_ty, s.len, 0), + c.LLVMConstInt(i64_ty, s.len, 0), }; return c.LLVMConstStructInContext(self.context, &fields, 2, 0); } @@ -5368,6 +5411,43 @@ pub const CodeGen = struct { return self.allocator.dupeZ(u8, name) catch unreachable; } + /// Convert a type expression AST node to its source string representation. + fn typeNodeToString(self: *CodeGen, node: *const Node) []const u8 { + return switch (node.data) { + .type_expr => |te| te.name, + .identifier => |id| id.name, + .pointer_type_expr => |pte| std.fmt.allocPrint(self.allocator, "*{s}", .{self.typeNodeToString(pte.pointee_type)}) catch "?", + .many_pointer_type_expr => |mpte| std.fmt.allocPrint(self.allocator, "[*]{s}", .{self.typeNodeToString(mpte.element_type)}) catch "?", + .slice_type_expr => |ste| std.fmt.allocPrint(self.allocator, "[]{s}", .{self.typeNodeToString(ste.element_type)}) catch "?", + .array_type_expr => |ate| blk: { + const elem = self.typeNodeToString(ate.element_type); + if (ate.length.data == .int_literal) { + break :blk std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ ate.length.data.int_literal.value, elem }) catch "?"; + } + break :blk std.fmt.allocPrint(self.allocator, "[?]{s}", .{elem}) catch "?"; + }, + else => "?", + }; + } + + /// Build a function type string like "() -> s32" from an fn_decl. + fn buildFnSignature(self: *CodeGen, fd: ast.FnDecl) []const u8 { + var buf = std.ArrayList(u8).empty; + buf.appendSlice(self.allocator, "(") catch return "?"; + for (fd.params, 0..) |param, i| { + if (i > 0) buf.appendSlice(self.allocator, ", ") catch {}; + if (param.is_variadic) buf.appendSlice(self.allocator, "..") catch {}; + const ty_str = self.typeNodeToString(param.type_expr); + buf.appendSlice(self.allocator, ty_str) catch {}; + } + buf.appendSlice(self.allocator, ")") catch {}; + if (fd.return_type) |rt| { + buf.appendSlice(self.allocator, " -> ") catch {}; + buf.appendSlice(self.allocator, self.typeNodeToString(rt)) catch {}; + } + return buf.toOwnedSlice(self.allocator) catch "?"; + } + /// Extract a qualified name from a callee expression (identifier or field_access chain). fn calleeToQualifiedName(self: *CodeGen, callee: *Node) ?[]const u8 { if (callee.data == .identifier) return callee.data.identifier.name; @@ -5415,7 +5495,7 @@ pub const CodeGen = struct { fn inferType(self: *CodeGen, node: *Node) Type { return switch (node.data) { - .int_literal => Type.s(32), + .int_literal => Type.s(64), .float_literal => .f32, .bool_literal => .boolean, .string_literal => .string_type, @@ -5435,7 +5515,7 @@ pub const CodeGen = struct { .identifier => |ident| { if (self.named_values.get(ident.name)) |entry| return entry.ty; if (self.comptime_globals.get(ident.name)) |ct| return ct.ty; - return Type.s(32); + return Type.s(64); }, .if_expr => |ie| { return self.inferType(ie.then_branch); @@ -5477,21 +5557,21 @@ pub const CodeGen = struct { }; if (obj_ty) |uty| return uty; } - const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(32); + const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(64); const base_name = if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |idx| callee_name[idx + 1 ..] else callee_name; // Built-in: sqrt returns same type as argument if (std.mem.eql(u8, base_name, "sqrt")) { if (call_node.args.len > 0) return self.inferType(call_node.args[0]); return .f32; } - // Built-in: size_of returns s32 - if (std.mem.eql(u8, base_name, "size_of")) return Type.s(32); - // Built-in: type_of returns s32 (type tag) - if (std.mem.eql(u8, base_name, "type_of")) return Type.s(32); + // Built-in: size_of returns s64 + if (std.mem.eql(u8, base_name, "size_of")) return Type.s(64); + // Built-in: type_of returns s64 (type tag) + if (std.mem.eql(u8, base_name, "type_of")) return Type.s(64); // Built-in: type_name returns string if (std.mem.eql(u8, base_name, "type_name")) return .string_type; - // Built-in: field_count returns s32 - if (std.mem.eql(u8, base_name, "field_count")) return Type.s(32); + // Built-in: field_count returns s64 + if (std.mem.eql(u8, base_name, "field_count")) return Type.s(64); // Built-in: field_name returns string if (std.mem.eql(u8, base_name, "field_name")) return .string_type; // Built-in: field_value returns Any @@ -5499,7 +5579,7 @@ pub const CodeGen = struct { // Built-in: cast returns the target type (first arg) if (std.mem.eql(u8, base_name, "cast")) { if (call_node.args.len > 0) return self.resolveType(call_node.args[0]); - return Type.s(32); + return Type.s(64); } // Built-in: alloc returns string if (std.mem.eql(u8, base_name, "alloc")) return .string_type; @@ -5507,7 +5587,7 @@ pub const CodeGen = struct { const template = self.generic_templates.get(callee_name) orelse blk: { // Intra-namespace fallback if (self.current_namespace) |ns| { - const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(32); + const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64); break :blk self.generic_templates.get(qualified); } break :blk null; @@ -5544,16 +5624,16 @@ pub const CodeGen = struct { const resolved = self.resolveType(rt); if (!std.meta.eql(resolved, Type.void_type)) return resolved; } - return Type.s(32); + return Type.s(64); } // Check non-generic LLVM functions - const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(32); + const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(64); var callee_fn_opt = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr); // Intra-namespace fallback if (callee_fn_opt == null) { if (self.current_namespace) |ns| { - const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(32); - const qz = self.allocator.dupeZ(u8, q) catch return Type.s(32); + const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64); + const qz = self.allocator.dupeZ(u8, q) catch return Type.s(64); callee_fn_opt = c.LLVMGetNamedFunction(self.module, qz.ptr); } } @@ -5562,44 +5642,44 @@ pub const CodeGen = struct { const ret_llvm = c.LLVMGetReturnType(fn_type); return self.llvmTypeToSxType(ret_llvm); } - return Type.s(32); + return Type.s(64); }, .unary_op => |unop| { if (unop.op == .address_of) { const operand_ty = self.inferType(unop.operand); - const name = operand_ty.displayName(self.allocator) catch return Type.s(32); + const name = operand_ty.displayName(self.allocator) catch return Type.s(64); return .{ .pointer_type = .{ .pointee_name = name } }; } return self.inferType(unop.operand); }, .deref_expr => |de| { const ptr_ty = self.inferType(de.operand); - if (ptr_ty.isPointer()) return self.resolveTypeFromName(ptr_ty.pointer_type.pointee_name) orelse Type.s(32); - return Type.s(32); + if (ptr_ty.isPointer()) return self.resolveTypeFromName(ptr_ty.pointer_type.pointee_name) orelse Type.s(64); + return Type.s(64); }, .null_literal => return .{ .pointer_type = .{ .pointee_name = "void" } }, .field_access => |fa| { var obj_ty = self.inferType(fa.object); // Auto-deref: if pointer, unwrap to pointee if (obj_ty.isPointer()) { - obj_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse Type.s(32); + obj_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse Type.s(64); } if (obj_ty == .string_type) { - if (std.mem.eql(u8, fa.field, "len")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); if (std.mem.eql(u8, fa.field, "ptr")) return .string_type; } if (obj_ty.isSlice()) { - if (std.mem.eql(u8, fa.field, "len")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); } if (obj_ty.isArray()) { - if (std.mem.eql(u8, fa.field, "len")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); } if (obj_ty.isAny()) { - if (std.mem.eql(u8, fa.field, "tag")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "tag")) return Type.s(64); if (std.mem.eql(u8, fa.field, "value")) return Type.s(64); } if (obj_ty.isVector()) { - return obj_ty.vectorElementType() orelse Type.s(32); + return obj_ty.vectorElementType() orelse Type.s(64); } if (obj_ty.isStruct()) { if (self.struct_types.get(obj_ty.struct_type)) |info| { @@ -5617,23 +5697,24 @@ pub const CodeGen = struct { } } } - return Type.s(32); + return Type.s(64); }, .index_expr => |ie| { const obj_ty = self.inferType(ie.object); + if (obj_ty == .string_type) return Type.u(8); if (obj_ty.isVector()) { - return obj_ty.vectorElementType() orelse Type.s(32); + return obj_ty.vectorElementType() orelse Type.s(64); } if (obj_ty.isArray()) { - return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(32); + return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(64); } if (obj_ty.isSlice()) { - return obj_ty.sliceElementType() orelse Type.s(32); + return obj_ty.sliceElementType() orelse Type.s(64); } if (obj_ty.isManyPointer()) { - return self.resolveTypeFromName(obj_ty.many_pointer_type.element_name) orelse Type.s(32); + return self.resolveTypeFromName(obj_ty.many_pointer_type.element_name) orelse Type.s(64); } - return Type.s(32); + return Type.s(64); }, .slice_expr => |se| { const obj_ty = self.inferType(se.object); @@ -5645,11 +5726,11 @@ pub const CodeGen = struct { .array_literal => |al| { if (al.elements.len == 0) return .void_type; const elem_ty = self.inferType(al.elements[0]); - const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(32); + const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(64); return .{ .array_type = .{ .element_name = elem_name, .length = @intCast(al.elements.len) } }; }, .while_expr, .for_expr, .break_expr, .continue_expr => .void_type, - else => Type.s(32), + else => Type.s(64), }; } diff --git a/src/comptime.zig b/src/comptime.zig index 50d6f61..5f23c87 100644 --- a/src/comptime.zig +++ b/src/comptime.zig @@ -15,6 +15,12 @@ pub const Value = union(enum) { array_val: ArrayValue, type_val: Type, function_val: FunctionVal, + pointer_val: PointerValue, + null_val: void, + + pub const PointerValue = struct { + target: [*]Value, + }; pub const StructValue = struct { type_name: []const u8, @@ -96,6 +102,11 @@ pub const Value = union(enum) { try buf.append(allocator, ']'); return buf.items; }, + .pointer_val => |pv| { + const inner = try pv.target[0].format(allocator); + return std.fmt.allocPrint(allocator, "&{s}", .{inner}); + }, + .null_val => allocator.dupe(u8, "null"), }; } }; @@ -160,6 +171,13 @@ pub const Instruction = union(enum) { get_field: u16, set_field: u16, + // Pointers + address_of_local: u16, // push pointer to local slot + address_of_index, // pop idx, pop array, push pointer to element + deref, // pop pointer, push dereferenced value + deref_set, // pop value, pop pointer, store through pointer + push_null, // push null pointer + // Arrays make_array: u32, // element count on stack get_index, @@ -295,6 +313,29 @@ pub const Compiler = struct { return idx; } + /// Look up a struct field index by name, handling pointer auto-deref. + fn resolveFieldIndex(self: *Compiler, object: *Node, field: []const u8) ?u16 { + if (self.sema_result) |sr| { + const obj_ty = sr.type_map.get(object) orelse return null; + const struct_name: ?[]const u8 = if (obj_ty.isStruct()) + obj_ty.struct_type + else if (obj_ty.isPointer()) + obj_ty.pointer_type.pointee_name + else + null; + if (struct_name) |sn| { + if (sr.struct_types.get(sn)) |info| { + for (info.field_names, 0..) |fname, idx| { + if (std.mem.eql(u8, fname, field)) { + return @intCast(idx); + } + } + } + } + } + return null; + } + fn resolveLocal(self: *Compiler, name: []const u8) ?u16 { var i = self.locals.items.len; while (i > 0) { @@ -451,12 +492,29 @@ pub const Compiler = struct { } }, .unary_op => |unop| { - try self.compileNode(unop.operand); - switch (unop.op) { - .negate => try self.emit(.negate), - .not => try self.emit(.not), - .xx => {}, // cast — handle later - .address_of => {}, // pointers not supported in comptime + if (unop.op == .address_of) { + if (unop.operand.data == .identifier) { + if (self.resolveLocal(unop.operand.data.identifier.name)) |slot| { + try self.emit(.{ .address_of_local = slot }); + } else { + return error.UnsupportedExpression; + } + } else if (unop.operand.data == .index_expr) { + // &arr[i] — push array, push index, address_of_index + try self.compileNode(unop.operand.data.index_expr.object); + try self.compileNode(unop.operand.data.index_expr.index); + try self.emit(.address_of_index); + } else { + return error.UnsupportedExpression; + } + } else { + try self.compileNode(unop.operand); + switch (unop.op) { + .negate => try self.emit(.negate), + .not => try self.emit(.not), + .xx => {}, // cast — handle later + .address_of => unreachable, // handled above + } } }, .comptime_expr => |ct| { @@ -533,6 +591,26 @@ pub const Compiler = struct { try self.emit(.{ .set_local = slot }); } } + } else if (asgn.target.data == .field_access) { + // obj.field = val (works with auto-deref for pointers) + const fa = asgn.target.data.field_access; + const field_idx = self.resolveFieldIndex(fa.object, fa.field) orelse return error.UnsupportedExpression; + try self.compileNode(fa.object); + if (asgn.op != .assign) return error.UnsupportedExpression; + try self.compileNode(asgn.value); + try self.emit(.{ .set_field = field_idx }); + // Store back to local + if (fa.object.data == .identifier) { + if (self.resolveLocal(fa.object.data.identifier.name)) |slot| { + try self.emit(.{ .set_local = slot }); + } + } + } else if (asgn.target.data == .deref_expr) { + // p.* = val + try self.compileNode(asgn.target.data.deref_expr.operand); + if (asgn.op != .assign) return error.UnsupportedExpression; + try self.compileNode(asgn.value); + try self.emit(.deref_set); } }, .return_stmt => |rs| { @@ -655,20 +733,10 @@ pub const Compiler = struct { } } } - // Look up field index from sema struct_types - if (self.sema_result) |sr| { - // Infer the object type to find the struct name - const obj_ty = sr.type_map.get(fa.object); - if (obj_ty != null and obj_ty.?.isStruct()) { - if (sr.struct_types.get(obj_ty.?.struct_type)) |info| { - for (info.field_names, 0..) |fname, idx| { - if (std.mem.eql(u8, fname, fa.field)) { - try self.emit(.{ .get_field = @intCast(idx) }); - return; - } - } - } - } + // Look up field index from sema struct_types (handles pointer auto-deref) + if (self.resolveFieldIndex(fa.object, fa.field)) |field_idx| { + try self.emit(.{ .get_field = field_idx }); + return; } // Fallback: use field name for well-known string fields // (sema may not have type info for nodes in imported function bodies) @@ -748,6 +816,16 @@ pub const Compiler = struct { const offset = @as(i32, @intCast(target)) - @as(i32, @intCast(self.instructions.items.len)) - 1; try self.emit(.{ .jump = offset }); }, + .deref_expr => |de| { + try self.compileNode(de.operand); + try self.emit(.deref); + }, + .null_literal => { + try self.emit(.push_null); + }, + .pointer_type_expr, .many_pointer_type_expr => { + try self.emit(.push_void); // type expressions not meaningful as values + }, .defer_stmt => {}, // defer not meaningful in comptime .insert_expr => {}, // handled by codegen, not VM else => { @@ -871,6 +949,51 @@ pub const VM = struct { self.stack[abs_slot] = val; }, + // Pointers + .address_of_local => |slot| { + const abs_slot = frame.base_slot + slot; + // Grow stack if needed so the target slot exists + while (self.sp <= abs_slot) { + self.stack[self.sp] = .{ .void_val = {} }; + self.sp += 1; + } + const ptr: [*]Value = @ptrCast(&self.stack[abs_slot]); + try self.push(.{ .pointer_val = .{ .target = ptr } }); + }, + .address_of_index => { + const idx_val = try self.pop(); + const arr = try self.pop(); + const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError); + if (arr == .array_val) { + if (idx >= arr.array_val.elements.len) return error.IndexOutOfBounds; + try self.push(.{ .pointer_val = .{ .target = arr.array_val.elements.ptr + idx } }); + } else { + return error.TypeError; + } + }, + .deref => { + const v = try self.pop(); + if (v == .pointer_val) { + try self.push(try self.cloneValue(v.pointer_val.target[0])); + } else if (v == .null_val) { + return error.NullDereference; + } else { + return error.TypeError; + } + }, + .deref_set => { + const val = try self.pop(); + const ptr_v = try self.pop(); + if (ptr_v == .pointer_val) { + ptr_v.pointer_val.target[0] = val; + } else if (ptr_v == .null_val) { + return error.NullDereference; + } else { + return error.TypeError; + } + }, + .push_null => try self.push(.{ .null_val = {} }), + // Global variables (lazily resolved from root_decls) .get_global => |name_idx| { const name = if (name_idx < frame.chunk.strings.len) frame.chunk.strings[name_idx] else return error.InvalidGlobal; @@ -1014,7 +1137,9 @@ pub const VM = struct { try self.push(.{ .struct_val = .{ .type_name = sm.type_name, .field_names = sm.field_names, .fields = fields } }); }, .get_field => |idx| { - const obj = try self.pop(); + const raw_obj = try self.pop(); + // Auto-deref pointer + const obj = if (raw_obj == .pointer_val) raw_obj.pointer_val.target[0] else raw_obj; if (obj == .struct_val) { if (idx < obj.struct_val.fields.len) { try self.push(obj.struct_val.fields[idx]); @@ -1034,12 +1159,22 @@ pub const VM = struct { }, .set_field => |idx| { const val = try self.pop(); - const obj = try self.pop(); - if (obj == .struct_val) { - if (idx < obj.struct_val.fields.len) { - obj.struct_val.fields[idx] = val; + const raw_obj = try self.pop(); + if (raw_obj == .pointer_val) { + // Auto-deref: mutate field in-place through pointer + const target = raw_obj.pointer_val.target; + if (target[0] == .struct_val) { + const sv = target[0].struct_val; + if (idx < sv.fields.len) { + sv.fields[idx] = val; + } } - try self.push(obj); + try self.push(raw_obj); // push pointer back + } else if (raw_obj == .struct_val) { + if (idx < raw_obj.struct_val.fields.len) { + raw_obj.struct_val.fields[idx] = val; + } + try self.push(raw_obj); } else { return error.TypeError; } @@ -1073,6 +1208,10 @@ pub const VM = struct { } else { return error.IndexOutOfBounds; } + } else if (arr == .pointer_val) { + // Many-pointer indexing: ptr[i] + const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError); + try self.push(arr.pointer_val.target[idx]); } else { return error.TypeError; } @@ -1096,6 +1235,11 @@ pub const VM = struct { mutable[idx] = byte_val; } try self.push(arr); + } else if (arr == .pointer_val) { + // Many-pointer index assignment: ptr[i] = val + const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError); + arr.pointer_val.target[idx] = val; + try self.push(arr); } else { return error.TypeError; } @@ -1166,11 +1310,37 @@ pub const VM = struct { } }; } + /// Clone a value, allocating new backing storage for composites (structs/arrays) + /// so the clone is independent. Does not follow pointers. + fn cloneValue(self: *VM, val: Value) !Value { + return switch (val) { + .struct_val => |sv| { + const new_fields = try self.allocator.alloc(Value, sv.fields.len); + for (sv.fields, 0..) |f, i| { + new_fields[i] = try self.cloneValue(f); + } + return .{ .struct_val = .{ .type_name = sv.type_name, .field_names = sv.field_names, .fields = new_fields } }; + }, + .array_val => |av| { + const new_elements = try self.allocator.alloc(Value, av.elements.len); + for (av.elements, 0..) |e, i| { + new_elements[i] = try self.cloneValue(e); + } + return .{ .array_val = .{ .elements = new_elements } }; + }, + else => val, + }; + } + fn valEqual(self: *VM, a: Value, b: Value) bool { _ = self; if (a == .int_val and b == .int_val) return a.int_val == b.int_val; if (a == .bool_val and b == .bool_val) return a.bool_val == b.bool_val; if (a == .string_val and b == .string_val) return std.mem.eql(u8, a.string_val, b.string_val); + // Pointer comparison + if (a == .null_val and b == .null_val) return true; + if (a == .null_val or b == .null_val) return false; + if (a == .pointer_val and b == .pointer_val) return a.pointer_val.target == b.pointer_val.target; // Float comparison const af = a.asFloat(); const bf = b.asFloat(); @@ -1377,6 +1547,7 @@ pub const VM = struct { StackUnderflow, IndexOutOfBounds, DivisionByZero, + NullDereference, UnsupportedExpression, OutOfMemory, }; @@ -1752,3 +1923,167 @@ test "VM: function with return statement" { ); try std.testing.expectEqual(@as(i64, 36), result.int_val); } + +test "VM: address-of and deref" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // x := 42; ptr := &x; ptr.* + const code = [_]Instruction{ + .{ .push_int = 42 }, + .{ .set_local = 0 }, // x = 42 + .{ .address_of_local = 0 }, // &x + .{ .set_local = 1 }, // ptr = &x + .{ .get_local = 1 }, // ptr + .deref, // ptr.* + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 2, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 42), result.int_val); +} + +test "VM: deref_set modifies through pointer" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // x := 10; ptr := &x; ptr.* = 99; x + const code = [_]Instruction{ + .{ .push_int = 10 }, + .{ .set_local = 0 }, // x = 10 + .{ .address_of_local = 0 }, // &x + .{ .set_local = 1 }, // ptr = &x + .{ .get_local = 1 }, // ptr + .{ .push_int = 99 }, + .deref_set, // ptr.* = 99 + .{ .get_local = 0 }, // x (should be 99 now) + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 2, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 99), result.int_val); +} + +test "VM: null pointer" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const code = [_]Instruction{ + .push_null, + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 0, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expect(result == .null_val); +} + +test "VM: pointer to struct field access" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // Build: struct{x: 10, y: 20}, &struct, get_field(1) — auto-deref + const code = [_]Instruction{ + .{ .push_int = 10 }, + .{ .push_int = 20 }, + .{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } }, + .{ .set_local = 0 }, // v = Point{10, 20} + .{ .address_of_local = 0 }, // &v + .{ .get_field = 1 }, // auto-deref, get y + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 1, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 20), result.int_val); +} + +test "VM: pointer to struct field mutation" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // v = Point{10, 20}; ptr = &v; ptr.x = 99; v.x + const code = [_]Instruction{ + .{ .push_int = 10 }, + .{ .push_int = 20 }, + .{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } }, + .{ .set_local = 0 }, // v = Point{10, 20} + .{ .address_of_local = 0 }, // &v + .{ .set_local = 1 }, // ptr = &v + .{ .get_local = 1 }, // ptr + .{ .push_int = 99 }, + .{ .set_field = 0 }, // ptr.x = 99 (auto-deref) + .{ .set_local = 1 }, // store ptr back + .{ .get_local = 0 }, // v + .{ .get_field = 0 }, // v.x + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 2, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 99), result.int_val); +} + +test "VM: many-pointer indexing" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // arr = [10, 20, 30]; mp = &arr[0]; mp[2] + const code = [_]Instruction{ + .{ .push_int = 10 }, + .{ .push_int = 20 }, + .{ .push_int = 30 }, + .{ .make_array = 3 }, + .{ .set_local = 0 }, // arr = [10, 20, 30] + .{ .get_local = 0 }, // arr + .{ .push_int = 0 }, + .address_of_index, // &arr[0] + .{ .set_local = 1 }, // mp = &arr[0] + .{ .get_local = 1 }, // mp + .{ .push_int = 2 }, + .get_index, // mp[2] + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 2, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 30), result.int_val); +} diff --git a/src/sema.zig b/src/sema.zig index 6e6be55..44c2138 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -125,7 +125,7 @@ pub const Analyzer = struct { var param_types = std.ArrayList(Type).empty; var has_variadic = false; for (fd.params) |param| { - const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(32); + const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(64); if (param.is_variadic) { has_variadic = true; // Variadic param becomes a slice type @@ -158,7 +158,7 @@ pub const Analyzer = struct { const lam = cd.value.data.lambda; var param_types = std.ArrayList(Type).empty; for (lam.params) |param| { - const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(32); + const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(64); try param_types.append(self.allocator, pt); } const ret = if (lam.return_type) |rt| Type.fromTypeExpr(rt) orelse .void_type else .void_type; @@ -185,7 +185,7 @@ pub const Analyzer = struct { // Populate struct_types registry var field_types = std.ArrayList(Type).empty; for (sd.field_types) |ft| { - const resolved = Type.fromTypeExpr(ft) orelse Type.s(32); + const resolved = Type.fromTypeExpr(ft) orelse Type.s(64); try field_types.append(self.allocator, resolved); } try self.struct_types.put(sd.name, .{ @@ -272,7 +272,7 @@ pub const Analyzer = struct { /// symbols for identifier types, and Type.widen for arithmetic promotion. pub fn inferExprType(self: *Analyzer, node: *const Node) Type { return switch (node.data) { - .int_literal => Type.s(32), + .int_literal => Type.s(64), .float_literal => .f32, .bool_literal => .boolean, .string_literal => .string_type, @@ -296,10 +296,10 @@ pub const Analyzer = struct { i -= 1; const sym = self.symbols.items[i]; if (sym.scope_depth <= self.scope_depth and std.mem.eql(u8, sym.name, ident.name)) { - return sym.ty orelse Type.s(32); + return sym.ty orelse Type.s(64); } } - return Type.s(32); + return Type.s(64); }, .if_expr => |ie| { return self.inferExprType(ie.then_branch); @@ -317,7 +317,7 @@ pub const Analyzer = struct { return .void_type; }, .call => |call_node| { - const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(32); + const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(64); // Check fn_signatures registry if (self.fn_signatures.get(callee_name)) |sig| { return sig.return_type; @@ -328,7 +328,7 @@ pub const Analyzer = struct { if (call_node.args.len > 0) return self.inferExprType(call_node.args[0]); return .f32; } - return Type.s(32); + return Type.s(64); }, .unary_op => |unop| { return self.inferExprType(unop.operand); @@ -336,7 +336,7 @@ pub const Analyzer = struct { .field_access => |fa| { const obj_ty = self.inferExprType(fa.object); if (obj_ty == .string_type) { - if (std.mem.eql(u8, fa.field, "len")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); if (std.mem.eql(u8, fa.field, "ptr")) return .string_type; } if (obj_ty.isStruct()) { @@ -349,17 +349,17 @@ pub const Analyzer = struct { } } if (obj_ty.isArray()) { - return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(32); + return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(64); } - return Type.s(32); + return Type.s(64); }, .index_expr => |ie| { const obj_ty = self.inferExprType(ie.object); - if (obj_ty == .string_type) return Type.s(32); + if (obj_ty == .string_type) return Type.u(8); if (obj_ty.isArray()) { - return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(32); + return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(64); } - return Type.s(32); + return Type.s(64); }, .slice_expr => |se| { const obj_ty = self.inferExprType(se.object); @@ -741,7 +741,7 @@ pub const Analyzer = struct { fn inferValueType(value: *Node) ?Type { return switch (value.data) { - .int_literal => Type.s(32), + .int_literal => Type.s(64), .float_literal => .f64, .bool_literal => .boolean, .string_literal => .string_type,