asm...
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
|||||||
zig-out
|
zig-out
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/
|
.vscode/
|
||||||
.sx-cache
|
.sx-cache
|
||||||
|
current/
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
#import "modules/math/math.sx";
|
#import "modules/math/math.sx";
|
||||||
|
#import "modules/compiler.sx";
|
||||||
pkg :: #import "modules/testpkg";
|
pkg :: #import "modules/testpkg";
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -3064,5 +3065,60 @@ END;
|
|||||||
print("opt-if5: {}\n", val2 ?? 0.0);
|
print("opt-if5: {}\n", val2 ?? 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- usize / isize ---
|
||||||
|
{
|
||||||
|
a : usize = 42;
|
||||||
|
b : isize = 0 - 7;
|
||||||
|
print("usize: {}\n", a);
|
||||||
|
print("isize: {}\n", b);
|
||||||
|
|
||||||
|
// arithmetic
|
||||||
|
c : usize = a + 8;
|
||||||
|
print("usize+8: {}\n", c);
|
||||||
|
|
||||||
|
// coercion from s32
|
||||||
|
x : s32 = 10;
|
||||||
|
y : usize = xx x;
|
||||||
|
print("s32->usize: {}\n", y);
|
||||||
|
|
||||||
|
// coercion to s64
|
||||||
|
z : s64 = xx a;
|
||||||
|
print("usize->s64: {}\n", z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- inline if (compile-time conditionals) ---
|
||||||
|
print("=== inline if ===\n");
|
||||||
|
{
|
||||||
|
// POINTER_SIZE is 8 on desktop (64-bit)
|
||||||
|
inline if POINTER_SIZE == 8 {
|
||||||
|
print("64-bit\n");
|
||||||
|
} else {
|
||||||
|
print("32-bit\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS enum comparison
|
||||||
|
inline if OS == .wasm {
|
||||||
|
print("wasm\n");
|
||||||
|
} else {
|
||||||
|
print("not wasm\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// != comparison
|
||||||
|
inline if OS != .unknown {
|
||||||
|
print("known os\n");
|
||||||
|
} else {
|
||||||
|
print("unknown os\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// nested inline if
|
||||||
|
inline if POINTER_SIZE != 4 {
|
||||||
|
inline if OS != .wasm {
|
||||||
|
print("desktop 64-bit\n");
|
||||||
|
} else {
|
||||||
|
print("wasm 64-bit??\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print("=== DONE ===\n");
|
print("=== DONE ===\n");
|
||||||
}
|
}
|
||||||
|
|||||||
6
examples/modules/compiler.sx
Normal file
6
examples/modules/compiler.sx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
OperatingSystem :: enum { macos; linux; windows; wasm; unknown; }
|
||||||
|
Architecture :: enum { aarch64; x86_64; wasm32; unknown; }
|
||||||
|
|
||||||
|
OS : OperatingSystem = .unknown;
|
||||||
|
ARCH : Architecture = .unknown;
|
||||||
|
POINTER_SIZE : s64 = 8;
|
||||||
@@ -49,11 +49,11 @@ glGenVertexArrays : (s32, *u32) -> void = ---;
|
|||||||
glGenBuffers : (s32, *u32) -> void = ---;
|
glGenBuffers : (s32, *u32) -> void = ---;
|
||||||
glBindVertexArray : (u32) -> void = ---;
|
glBindVertexArray : (u32) -> void = ---;
|
||||||
glBindBuffer : (u32, u32) -> void = ---;
|
glBindBuffer : (u32, u32) -> void = ---;
|
||||||
glBufferData : (u32, s64, *void, u32) -> void = ---;
|
glBufferData : (u32, isize, *void, u32) -> void = ---;
|
||||||
glVertexAttribPointer : (u32, s32, u32, u8, s32, *void) -> void = ---;
|
glVertexAttribPointer : (u32, s32, u32, u8, s32, *void) -> void = ---;
|
||||||
glEnableVertexAttribArray : (u32) -> void = ---;
|
glEnableVertexAttribArray : (u32) -> void = ---;
|
||||||
glGetUniformLocation : (u32, [:0]u8) -> s32 = ---;
|
glGetUniformLocation : (u32, [*]u8) -> s32 = ---;
|
||||||
glUniformMatrix4fv : (s32, s32, u8, [16]f32) -> void = ---;
|
glUniformMatrix4fv : (s32, s32, u8, [*]f32) -> void = ---;
|
||||||
glUniform3f : (s32, f32, f32, f32) -> void = ---;
|
glUniform3f : (s32, f32, f32, f32) -> void = ---;
|
||||||
glDepthFunc : (u32) -> void = ---;
|
glDepthFunc : (u32) -> void = ---;
|
||||||
glUniform1f : (s32, f32) -> void = ---;
|
glUniform1f : (s32, f32) -> void = ---;
|
||||||
@@ -72,10 +72,11 @@ GL_ONE_MINUS_SRC_ALPHA :u32: 0x0303;
|
|||||||
GL_TEXTURE0 :u32: 0x84C0;
|
GL_TEXTURE0 :u32: 0x84C0;
|
||||||
GL_LINEAR :u32: 0x2601;
|
GL_LINEAR :u32: 0x2601;
|
||||||
GL_RED :u32: 0x1903;
|
GL_RED :u32: 0x1903;
|
||||||
|
GL_R8 :u32: 0x8229;
|
||||||
GL_UNPACK_ALIGNMENT :u32: 0x0CF5;
|
GL_UNPACK_ALIGNMENT :u32: 0x0CF5;
|
||||||
|
|
||||||
glScissor : (s32, s32, s32, s32) -> void = ---;
|
glScissor : (s32, s32, s32, s32) -> void = ---;
|
||||||
glBufferSubData : (u32, s64, s64, *void) -> void = ---;
|
glBufferSubData : (u32, isize, isize, *void) -> void = ---;
|
||||||
glGenTextures : (s32, *u32) -> void = ---;
|
glGenTextures : (s32, *u32) -> void = ---;
|
||||||
glBindTexture : (u32, u32) -> void = ---;
|
glBindTexture : (u32, u32) -> void = ---;
|
||||||
glTexImage2D : (u32, s32, s32, s32, s32, s32, u32, u32, *void) -> void = ---;
|
glTexImage2D : (u32, s32, s32, s32, s32, s32, u32, u32, *void) -> void = ---;
|
||||||
@@ -88,7 +89,7 @@ glPixelStorei : (u32, s32) -> void = ---;
|
|||||||
|
|
||||||
// Loader: call once after creating GL context
|
// Loader: call once after creating GL context
|
||||||
// Pass in a proc loader (e.g. SDL_GL_GetProcAddress)
|
// Pass in a proc loader (e.g. SDL_GL_GetProcAddress)
|
||||||
load_gl :: (get_proc: ([:0]u8) -> *void) {
|
load_gl :: (get_proc: ([*]u8) -> *void) {
|
||||||
glClearColor = xx get_proc("glClearColor");
|
glClearColor = xx get_proc("glClearColor");
|
||||||
glClear = xx get_proc("glClear");
|
glClear = xx get_proc("glClear");
|
||||||
glEnable = xx get_proc("glEnable");
|
glEnable = xx get_proc("glEnable");
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ SDL_GL_CONTEXT_PROFILE_MASK :s32: 20;
|
|||||||
|
|
||||||
// SDL_GLProfile
|
// SDL_GLProfile
|
||||||
SDL_GL_CONTEXT_PROFILE_CORE :s32: 0x1;
|
SDL_GL_CONTEXT_PROFILE_CORE :s32: 0x1;
|
||||||
|
SDL_GL_CONTEXT_PROFILE_ES :s32: 0x4;
|
||||||
|
|
||||||
// SDL_GLContextFlag
|
// SDL_GLContextFlag
|
||||||
SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG :s32: 0x2;
|
SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG :s32: 0x2;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ setsockopt :: (fd: s32, level: s32, optname: s32, optval: *s32, optlen: u32) ->
|
|||||||
bind :: (fd: s32, addr: *SockAddr, addrlen: u32) -> s32 #foreign libc;
|
bind :: (fd: s32, addr: *SockAddr, addrlen: u32) -> s32 #foreign libc;
|
||||||
listen :: (fd: s32, backlog: s32) -> s32 #foreign libc;
|
listen :: (fd: s32, backlog: s32) -> s32 #foreign libc;
|
||||||
accept :: (fd: s32, addr: *SockAddr, addrlen: *u32) -> s32 #foreign libc;
|
accept :: (fd: s32, addr: *SockAddr, addrlen: *u32) -> s32 #foreign libc;
|
||||||
read :: (fd: s32, buf: [*]u8, count: s64) -> s64 #foreign libc;
|
read :: (fd: s32, buf: [*]u8, count: usize) -> isize #foreign libc;
|
||||||
write :: (fd: s32, buf: [*]u8, count: s64) -> s64 #foreign libc;
|
write :: (fd: s32, buf: [*]u8, count: usize) -> isize #foreign libc;
|
||||||
close :: (fd: s32) -> s32 #foreign libc;
|
close :: (fd: s32) -> s32 #foreign libc;
|
||||||
|
|
||||||
// Constants (macOS)
|
// Constants (macOS)
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ pub const IfExpr = struct {
|
|||||||
then_branch: *Node,
|
then_branch: *Node,
|
||||||
else_branch: ?*Node,
|
else_branch: ?*Node,
|
||||||
is_inline: bool, // true for `if cond then a else b`
|
is_inline: bool, // true for `if cond then a else b`
|
||||||
|
is_comptime: bool = false, // true for `inline if` — compile-time branch elimination
|
||||||
binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding
|
binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -303,6 +303,53 @@ pub fn loadCObjectsForJIT(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compile C sources using emcc for Emscripten/WASM targets.
|
||||||
|
/// Shells out to `emcc -c` for each source file, returns temp object file paths.
|
||||||
|
pub fn compileCWithEmcc(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
infos: []const CImportInfo,
|
||||||
|
) ![]const []const u8 {
|
||||||
|
var paths = std.ArrayList([]const u8).empty;
|
||||||
|
var obj_idx: usize = 0;
|
||||||
|
|
||||||
|
for (infos) |info| {
|
||||||
|
if (info.sources.len == 0) continue;
|
||||||
|
|
||||||
|
for (info.sources) |src| {
|
||||||
|
const out_path = try std.fmt.allocPrint(allocator, "/tmp/sx_emcc_{d}.o", .{obj_idx});
|
||||||
|
obj_idx += 1;
|
||||||
|
|
||||||
|
var argv = std.ArrayList([]const u8).empty;
|
||||||
|
try argv.appendSlice(allocator, &.{ "emcc", "-c", "-O2", src, "-o", out_path });
|
||||||
|
// Add include paths
|
||||||
|
for (info.includes) |inc| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-I{s}", .{dirName(inc)}));
|
||||||
|
}
|
||||||
|
for (info.defines) |def| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-D{s}", .{def}));
|
||||||
|
}
|
||||||
|
for (info.flags) |flag| {
|
||||||
|
try argv.append(allocator, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
const argv_slice = try argv.toOwnedSlice(allocator);
|
||||||
|
var child = std.process.spawn(io, .{ .argv = argv_slice }) catch {
|
||||||
|
std.debug.print("error: failed to spawn emcc for '{s}'\n", .{src});
|
||||||
|
return error.CompileError;
|
||||||
|
};
|
||||||
|
const result = child.wait(io) catch return error.CompileError;
|
||||||
|
if (result != .exited or result.exited != 0) {
|
||||||
|
std.debug.print("error: emcc failed for '{s}'\n", .{src});
|
||||||
|
return error.CompileError;
|
||||||
|
}
|
||||||
|
try paths.append(allocator, out_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return try paths.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
/// For build mode: write .o buffers to temp files, return paths for the linker.
|
/// For build mode: write .o buffers to temp files, return paths for the linker.
|
||||||
pub fn writeCObjectFiles(
|
pub fn writeCObjectFiles(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|||||||
@@ -116,9 +116,14 @@ pub const Compilation = struct {
|
|||||||
pub fn lowerToIR(self: *Compilation) ir.Module {
|
pub fn lowerToIR(self: *Compilation) ir.Module {
|
||||||
const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator);
|
const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator);
|
||||||
var module = ir.Module.init(self.allocator);
|
var module = ir.Module.init(self.allocator);
|
||||||
|
//TODO: find a better place for this
|
||||||
|
if (self.target_config.isWasm()) {
|
||||||
|
module.types.pointer_size = 4;
|
||||||
|
}
|
||||||
var lowering = ir.Lowering.init(&module);
|
var lowering = ir.Lowering.init(&module);
|
||||||
lowering.main_file = self.file_path;
|
lowering.main_file = self.file_path;
|
||||||
lowering.resolved_root = root;
|
lowering.resolved_root = root;
|
||||||
|
lowering.target_config = self.target_config;
|
||||||
lowering.lowerRoot(root);
|
lowering.lowerRoot(root);
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2103,8 +2103,9 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// int→int: SExt, ZExt, or Trunc
|
// int→int: SExt, ZExt, or Trunc
|
||||||
const from_bits = intBits(from);
|
const ptr_bits: u32 = @as(u32, self.ir_mod.types.pointer_size) * 8;
|
||||||
const to_bits = intBits(to);
|
const from_bits = if (intBits(from) == 0) ptr_bits else intBits(from);
|
||||||
|
const to_bits = if (intBits(to) == 0) ptr_bits else intBits(to);
|
||||||
if (to_bits > from_bits) {
|
if (to_bits > from_bits) {
|
||||||
return if (isSignedType(from))
|
return if (isSignedType(from))
|
||||||
c.LLVMBuildSExt(self.builder, operand, to_ty, "sext")
|
c.LLVMBuildSExt(self.builder, operand, to_ty, "sext")
|
||||||
@@ -2539,6 +2540,7 @@ pub const LLVMEmitter = struct {
|
|||||||
.string => self.getStringStructType(),
|
.string => self.getStringStructType(),
|
||||||
.any => self.getAnyStructType(),
|
.any => self.getAnyStructType(),
|
||||||
.noreturn => self.cached_void,
|
.noreturn => self.cached_void,
|
||||||
|
.isize, .usize => if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64,
|
||||||
else => self.toLLVMTypeInfo(ty),
|
else => self.toLLVMTypeInfo(ty),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -2665,6 +2667,7 @@ pub const LLVMEmitter = struct {
|
|||||||
// For now, use opaque ptr
|
// For now, use opaque ptr
|
||||||
return self.cached_ptr;
|
return self.cached_ptr;
|
||||||
},
|
},
|
||||||
|
.usize, .isize => if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2684,12 +2687,11 @@ pub const LLVMEmitter = struct {
|
|||||||
if (info == .slice) return self.cached_ptr;
|
if (info == .slice) return self.cached_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// WASM32: i64 → i32 for C ABI (size_t/ssize_t are 32-bit on wasm32)
|
// WASM32: usize/isize are pointer-sized (i32 on wasm32).
|
||||||
|
// Other integer types (s64, u64) keep their declared size — they represent
|
||||||
|
// genuinely 64-bit values (SDL_WindowFlags, timestamps, etc.).
|
||||||
if (self.target_config.isWasm()) {
|
if (self.target_config.isWasm()) {
|
||||||
if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(llvm_ty) == 64) {
|
if (ir_ty == .usize or ir_ty == .isize) return self.cached_i32;
|
||||||
// s64/u64 in extern decls → i32 on wasm32 (matches C's size_t, ssize_t, etc.)
|
|
||||||
return self.cached_i32;
|
|
||||||
}
|
|
||||||
return llvm_ty;
|
return llvm_ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3075,7 +3077,7 @@ fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool {
|
|||||||
|
|
||||||
fn isSignedType(ty: TypeId) bool {
|
fn isSignedType(ty: TypeId) bool {
|
||||||
return switch (ty) {
|
return switch (ty) {
|
||||||
.s8, .s16, .s32, .s64 => true,
|
.s8, .s16, .s32, .s64, .isize => true,
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -3095,6 +3097,7 @@ fn intBits(ty: TypeId) u32 {
|
|||||||
.s32, .u32 => 32,
|
.s32, .u32 => 32,
|
||||||
.s64, .u64 => 64,
|
.s64, .u64 => 64,
|
||||||
.bool => 1,
|
.bool => 1,
|
||||||
|
.usize, .isize => 0, // target-dependent — caller must query pointer_size
|
||||||
else => 64,
|
else => 64,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
184
src/ir/lower.zig
184
src/ir/lower.zig
@@ -104,6 +104,13 @@ pub const Lowering = struct {
|
|||||||
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
|
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
|
||||||
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // type alias name → target TypeId
|
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // type alias name → target TypeId
|
||||||
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name
|
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name
|
||||||
|
target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if)
|
||||||
|
comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH)
|
||||||
|
|
||||||
|
pub const ComptimeValue = union(enum) {
|
||||||
|
int_val: i64,
|
||||||
|
enum_tag: struct { ty: TypeId, tag: u32 },
|
||||||
|
};
|
||||||
|
|
||||||
const StructConstInfo = struct {
|
const StructConstInfo = struct {
|
||||||
value: *const Node,
|
value: *const Node,
|
||||||
@@ -165,12 +172,68 @@ pub const Lowering = struct {
|
|||||||
};
|
};
|
||||||
// Pass 1: scan — register all function ASTs, struct types, extern stubs
|
// Pass 1: scan — register all function ASTs, struct types, extern stubs
|
||||||
self.scanDecls(decls);
|
self.scanDecls(decls);
|
||||||
|
// Pass 1b: inject compile-time constants (OS, ARCH, POINTER_SIZE) from target config
|
||||||
|
self.injectComptimeConstants();
|
||||||
// Pass 2: lower main (and comptime side-effects)
|
// Pass 2: lower main (and comptime side-effects)
|
||||||
self.lowerMainAndComptime(decls);
|
self.lowerMainAndComptime(decls);
|
||||||
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
|
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
|
||||||
self.lowerDeferredTypeFns();
|
self.lowerDeferredTypeFns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inject compile-time constants from target_config into comptime_constants.
|
||||||
|
/// Called after scanDecls so that enum types (OperatingSystem, Architecture) are registered.
|
||||||
|
fn injectComptimeConstants(self: *Lowering) void {
|
||||||
|
const tc = self.target_config orelse return;
|
||||||
|
|
||||||
|
// OS: OperatingSystem enum { macos; linux; windows; wasm; unknown; }
|
||||||
|
const os_name_id = self.module.types.internString("OperatingSystem");
|
||||||
|
if (self.module.types.findByName(os_name_id)) |os_ty| {
|
||||||
|
const os_info = self.module.types.get(os_ty);
|
||||||
|
if (os_info == .@"enum") {
|
||||||
|
const tag: u32 = if (tc.isWasm())
|
||||||
|
self.findVariantIndex(os_info.@"enum".variants, "wasm")
|
||||||
|
else if (tc.isWindows())
|
||||||
|
self.findVariantIndex(os_info.@"enum".variants, "windows")
|
||||||
|
else if (tc.isLinux())
|
||||||
|
self.findVariantIndex(os_info.@"enum".variants, "linux")
|
||||||
|
else if (tc.isMacOS())
|
||||||
|
self.findVariantIndex(os_info.@"enum".variants, "macos")
|
||||||
|
else
|
||||||
|
self.findVariantIndex(os_info.@"enum".variants, "unknown");
|
||||||
|
self.comptime_constants.put("OS", .{ .enum_tag = .{ .ty = os_ty, .tag = tag } }) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARCH: Architecture enum { aarch64; x86_64; wasm32; unknown; }
|
||||||
|
const arch_name_id = self.module.types.internString("Architecture");
|
||||||
|
if (self.module.types.findByName(arch_name_id)) |arch_ty| {
|
||||||
|
const arch_info = self.module.types.get(arch_ty);
|
||||||
|
if (arch_info == .@"enum") {
|
||||||
|
const tag: u32 = if (tc.isWasm())
|
||||||
|
self.findVariantIndex(arch_info.@"enum".variants, "wasm32")
|
||||||
|
else if (tc.isAarch64())
|
||||||
|
self.findVariantIndex(arch_info.@"enum".variants, "aarch64")
|
||||||
|
else if (tc.isX86_64())
|
||||||
|
self.findVariantIndex(arch_info.@"enum".variants, "x86_64")
|
||||||
|
else
|
||||||
|
self.findVariantIndex(arch_info.@"enum".variants, "unknown");
|
||||||
|
self.comptime_constants.put("ARCH", .{ .enum_tag = .{ .ty = arch_ty, .tag = tag } }) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POINTER_SIZE: s64 (4 for wasm, 8 otherwise)
|
||||||
|
const ptr_size: i64 = if (tc.isWasm()) 4 else 8;
|
||||||
|
self.comptime_constants.put("POINTER_SIZE", .{ .int_val = ptr_size }) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findVariantIndex(self: *Lowering, variants: []const types.StringId, name: []const u8) u32 {
|
||||||
|
const name_id = self.module.types.internString(name);
|
||||||
|
for (variants, 0..) |v, i| {
|
||||||
|
if (v == name_id) return @intCast(i);
|
||||||
|
}
|
||||||
|
return 0; // fallback to first variant
|
||||||
|
}
|
||||||
|
|
||||||
/// Lower functions that were deferred because they use type-category matching.
|
/// Lower functions that were deferred because they use type-category matching.
|
||||||
/// At this point, main is fully lowered and all types are in the TypeTable.
|
/// At this point, main is fully lowered and all types are in the TypeTable.
|
||||||
fn lowerDeferredTypeFns(self: *Lowering) void {
|
fn lowerDeferredTypeFns(self: *Lowering) void {
|
||||||
@@ -381,6 +444,8 @@ pub const Lowering = struct {
|
|||||||
const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) {
|
const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) {
|
||||||
.undef_literal => .zeroinit,
|
.undef_literal => .zeroinit,
|
||||||
.int_literal => |il| .{ .int = il.value },
|
.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) },
|
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||||
else => null,
|
else => null,
|
||||||
} else null;
|
} else null;
|
||||||
@@ -723,6 +788,15 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lower an `inline if` branch — block body emits statements, expression returns value.
|
||||||
|
fn lowerInlineBranch(self: *Lowering, node: *const Node) Ref {
|
||||||
|
if (node.data == .block) {
|
||||||
|
self.lowerBlock(node);
|
||||||
|
return self.builder.constInt(0, .void);
|
||||||
|
}
|
||||||
|
return self.lowerExpr(node);
|
||||||
|
}
|
||||||
|
|
||||||
/// Lower a block and return the last expression's value (for implicit returns).
|
/// Lower a block and return the last expression's value (for implicit returns).
|
||||||
fn lowerBlockValue(self: *Lowering, node: *const Node) ?Ref {
|
fn lowerBlockValue(self: *Lowering, node: *const Node) ?Ref {
|
||||||
// Set force_block_value so nested if-else expressions produce values
|
// Set force_block_value so nested if-else expressions produce values
|
||||||
@@ -1845,6 +1919,19 @@ pub const Lowering = struct {
|
|||||||
// ── Control flow ────────────────────────────────────────────────
|
// ── Control flow ────────────────────────────────────────────────
|
||||||
|
|
||||||
fn lowerIfExpr(self: *Lowering, ie: *const ast.IfExpr) Ref {
|
fn lowerIfExpr(self: *Lowering, ie: *const ast.IfExpr) Ref {
|
||||||
|
// inline if: evaluate condition at compile time, only lower taken branch
|
||||||
|
if (ie.is_comptime) {
|
||||||
|
if (self.evalComptimeCondition(ie.condition)) |is_true| {
|
||||||
|
if (is_true) {
|
||||||
|
return self.lowerInlineBranch(ie.then_branch);
|
||||||
|
} else if (ie.else_branch) |eb| {
|
||||||
|
return self.lowerInlineBranch(eb);
|
||||||
|
}
|
||||||
|
return self.builder.constInt(0, .void);
|
||||||
|
}
|
||||||
|
// Condition couldn't be evaluated — fall through to runtime
|
||||||
|
}
|
||||||
|
|
||||||
// Check for constant-bool conditions (e.g., is_flags(T) → false) to avoid dead-code LLVM errors
|
// Check for constant-bool conditions (e.g., is_flags(T) → false) to avoid dead-code LLVM errors
|
||||||
if (self.tryConstBoolCondition(ie.condition)) |is_true| {
|
if (self.tryConstBoolCondition(ie.condition)) |is_true| {
|
||||||
if (is_true) {
|
if (is_true) {
|
||||||
@@ -1999,6 +2086,46 @@ pub const Lowering = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate a compile-time condition for `inline if`.
|
||||||
|
/// Handles: `ident == .variant`, `ident != .variant`, `ident == int`, `ident != int`.
|
||||||
|
fn evalComptimeCondition(self: *Lowering, node: *const Node) ?bool {
|
||||||
|
if (node.data != .binary_op) return null;
|
||||||
|
const bo = &node.data.binary_op;
|
||||||
|
if (bo.op != .eq and bo.op != .neq) return null;
|
||||||
|
|
||||||
|
// LHS must be an identifier that's in comptime_constants
|
||||||
|
const name = switch (bo.lhs.data) {
|
||||||
|
.identifier => |id| id.name,
|
||||||
|
else => return null,
|
||||||
|
};
|
||||||
|
const cv = self.comptime_constants.get(name) orelse return null;
|
||||||
|
|
||||||
|
switch (cv) {
|
||||||
|
.enum_tag => |et| {
|
||||||
|
// RHS must be an enum literal (.variant)
|
||||||
|
const variant_name = switch (bo.rhs.data) {
|
||||||
|
.enum_literal => |el| el.name,
|
||||||
|
else => return null,
|
||||||
|
};
|
||||||
|
// Look up variant index in the enum type
|
||||||
|
const enum_info = self.module.types.get(et.ty);
|
||||||
|
if (enum_info != .@"enum") return null;
|
||||||
|
const variant_idx = self.findVariantIndex(enum_info.@"enum".variants, variant_name);
|
||||||
|
const result = et.tag == variant_idx;
|
||||||
|
return if (bo.op == .eq) result else !result;
|
||||||
|
},
|
||||||
|
.int_val => |iv| {
|
||||||
|
// RHS must be an integer literal
|
||||||
|
const rhs_val: i64 = switch (bo.rhs.data) {
|
||||||
|
.int_literal => |il| il.value,
|
||||||
|
else => return null,
|
||||||
|
};
|
||||||
|
const result = iv == rhs_val;
|
||||||
|
return if (bo.op == .eq) result else !result;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref {
|
fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref {
|
||||||
const header_bb = self.freshBlock("while.hdr");
|
const header_bb = self.freshBlock("while.hdr");
|
||||||
const body_bb = self.freshBlock("while.body");
|
const body_bb = self.freshBlock("while.body");
|
||||||
@@ -2857,6 +2984,10 @@ pub const Lowering = struct {
|
|||||||
const info = self.module.types.get(obj_ty);
|
const info = self.module.types.get(obj_ty);
|
||||||
switch (info) {
|
switch (info) {
|
||||||
.tagged_union => |u| {
|
.tagged_union => |u| {
|
||||||
|
// .tag → extract the enum tag value with the correct tag type
|
||||||
|
if (std.mem.eql(u8, field, "tag")) {
|
||||||
|
return self.builder.emit(.{ .enum_tag = .{ .operand = obj } }, u.tag_type);
|
||||||
|
}
|
||||||
// Tagged union — use enum_payload
|
// Tagged union — use enum_payload
|
||||||
for (u.fields, 0..) |f, i| {
|
for (u.fields, 0..) |f, i| {
|
||||||
if (f.name == field_name_id) {
|
if (f.name == field_name_id) {
|
||||||
@@ -3727,8 +3858,19 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Method call: obj.method(args) → prepend obj (or &obj for *Self receivers)
|
// Method call: obj.method(args) → prepend obj (or &obj for *Self receivers)
|
||||||
const obj_ty = self.inferExprType(fa.object);
|
// For ptr.*.method(): pass the pointer directly instead of loading + re-addressing.
|
||||||
const obj = self.lowerExpr(fa.object);
|
// This ensures mutations through self: *T are visible after the call.
|
||||||
|
var obj_ty: TypeId = undefined;
|
||||||
|
var obj: Ref = undefined;
|
||||||
|
var effective_obj_node: *const Node = fa.object;
|
||||||
|
if (fa.object.data == .deref_expr) {
|
||||||
|
effective_obj_node = fa.object.data.deref_expr.operand;
|
||||||
|
obj_ty = self.inferExprType(effective_obj_node);
|
||||||
|
obj = self.lowerExpr(effective_obj_node);
|
||||||
|
} else {
|
||||||
|
obj_ty = self.inferExprType(fa.object);
|
||||||
|
obj = self.lowerExpr(fa.object);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if field is a closure type — call as closure, not method
|
// Check if field is a closure type — call as closure, not method
|
||||||
if (!obj_ty.isBuiltin()) {
|
if (!obj_ty.isBuiltin()) {
|
||||||
@@ -3781,7 +3923,7 @@ pub const Lowering = struct {
|
|||||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
const ret_ty = func.ret;
|
const ret_ty = func.ret;
|
||||||
const params = func.params;
|
const params = func.params;
|
||||||
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty);
|
self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
|
||||||
self.coerceCallArgs(method_args.items, params);
|
self.coerceCallArgs(method_args.items, params);
|
||||||
return self.builder.call(fid, method_args.items, ret_ty);
|
return self.builder.call(fid, method_args.items, ret_ty);
|
||||||
}
|
}
|
||||||
@@ -3800,7 +3942,7 @@ pub const Lowering = struct {
|
|||||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
const ret_ty = func.ret;
|
const ret_ty = func.ret;
|
||||||
const params = func.params;
|
const params = func.params;
|
||||||
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty);
|
self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
|
||||||
// Note: coerceCallArgs can trigger protocol thunk creation
|
// Note: coerceCallArgs can trigger protocol thunk creation
|
||||||
// (module.addFunction), invalidating func pointer.
|
// (module.addFunction), invalidating func pointer.
|
||||||
// Use pre-extracted params/ret_ty instead of func.* after this.
|
// Use pre-extracted params/ret_ty instead of func.* after this.
|
||||||
@@ -4005,6 +4147,14 @@ pub const Lowering = struct {
|
|||||||
if (!tt.isBuiltin()) {
|
if (!tt.isBuiltin()) {
|
||||||
const tti = self.module.types.get(tt);
|
const tti = self.module.types.get(tt);
|
||||||
if (tti == .closure) break :blk tti.closure.params;
|
if (tti == .closure) break :blk tti.closure.params;
|
||||||
|
// Unwrap ?Closure(...) → Closure(...)
|
||||||
|
if (tti == .optional) {
|
||||||
|
const inner = tti.optional.child;
|
||||||
|
if (!inner.isBuiltin()) {
|
||||||
|
const inner_info = self.module.types.get(inner);
|
||||||
|
if (inner_info == .closure) break :blk inner_info.closure.params;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break :blk null;
|
break :blk null;
|
||||||
} else null;
|
} else null;
|
||||||
@@ -4031,6 +4181,14 @@ pub const Lowering = struct {
|
|||||||
if (!tt.isBuiltin()) {
|
if (!tt.isBuiltin()) {
|
||||||
const tti = self.module.types.get(tt);
|
const tti = self.module.types.get(tt);
|
||||||
if (tti == .closure) break :blk tti.closure.ret;
|
if (tti == .closure) break :blk tti.closure.ret;
|
||||||
|
// Unwrap ?Closure(...) → Closure(...)
|
||||||
|
if (tti == .optional) {
|
||||||
|
const inner = tti.optional.child;
|
||||||
|
if (!inner.isBuiltin()) {
|
||||||
|
const inner_info = self.module.types.get(inner);
|
||||||
|
if (inner_info == .closure) break :blk inner_info.closure.ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Arrow lambda without explicit return type — infer from body expression
|
// Arrow lambda without explicit return type — infer from body expression
|
||||||
@@ -4388,8 +4546,7 @@ pub const Lowering = struct {
|
|||||||
offset = (offset + field_align - 1) & ~(field_align - 1);
|
offset = (offset + field_align - 1) & ~(field_align - 1);
|
||||||
offset += field_size;
|
offset += field_size;
|
||||||
}
|
}
|
||||||
// Align total to struct alignment (max field alignment, minimum 8)
|
// Align total to max field alignment (matches LLVM's struct alignment)
|
||||||
if (max_align < 8) max_align = 8;
|
|
||||||
return (offset + max_align - 1) & ~(max_align - 1);
|
return (offset + max_align - 1) & ~(max_align - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5716,6 +5873,8 @@ pub const Lowering = struct {
|
|||||||
if (ty == .void) return "void";
|
if (ty == .void) return "void";
|
||||||
if (ty == .string) return "string";
|
if (ty == .string) return "string";
|
||||||
if (ty == .any) return "Any";
|
if (ty == .any) return "Any";
|
||||||
|
if (ty == .usize) return "usize";
|
||||||
|
if (ty == .isize) return "isize";
|
||||||
|
|
||||||
const info = self.module.types.get(ty);
|
const info = self.module.types.get(ty);
|
||||||
return switch (info) {
|
return switch (info) {
|
||||||
@@ -5880,6 +6039,8 @@ pub const Lowering = struct {
|
|||||||
if (ty == .void) return "void";
|
if (ty == .void) return "void";
|
||||||
if (ty == .string) return "string";
|
if (ty == .string) return "string";
|
||||||
if (ty == .any) return "Any";
|
if (ty == .any) return "Any";
|
||||||
|
if (ty == .usize) return "usize";
|
||||||
|
if (ty == .isize) return "isize";
|
||||||
|
|
||||||
const info = self.module.types.get(ty);
|
const info = self.module.types.get(ty);
|
||||||
return switch (info) {
|
return switch (info) {
|
||||||
@@ -5932,6 +6093,8 @@ pub const Lowering = struct {
|
|||||||
tags.append(self.alloc, TypeId.u16.index()) catch {};
|
tags.append(self.alloc, TypeId.u16.index()) catch {};
|
||||||
tags.append(self.alloc, TypeId.u32.index()) catch {};
|
tags.append(self.alloc, TypeId.u32.index()) catch {};
|
||||||
tags.append(self.alloc, TypeId.u64.index()) catch {};
|
tags.append(self.alloc, TypeId.u64.index()) catch {};
|
||||||
|
tags.append(self.alloc, TypeId.usize.index()) catch {};
|
||||||
|
tags.append(self.alloc, TypeId.isize.index()) catch {};
|
||||||
return tags.items;
|
return tags.items;
|
||||||
}
|
}
|
||||||
if (std.mem.eql(u8, name, "float")) {
|
if (std.mem.eql(u8, name, "float")) {
|
||||||
@@ -7220,6 +7383,7 @@ pub const Lowering = struct {
|
|||||||
// Build call args: ctx + user args
|
// Build call args: ctx + user args
|
||||||
// Protocol method params use *void for Self-typed params. If the caller passes
|
// Protocol method params use *void for Self-typed params. If the caller passes
|
||||||
// a struct value, we need to alloca+store and pass the pointer instead.
|
// a struct value, we need to alloca+store and pass the pointer instead.
|
||||||
|
// Also coerce argument types to match declared param types (e.g., s64 → s32).
|
||||||
var call_args = std.ArrayList(Ref).empty;
|
var call_args = std.ArrayList(Ref).empty;
|
||||||
defer call_args.deinit(self.alloc);
|
defer call_args.deinit(self.alloc);
|
||||||
call_args.append(self.alloc, ctx) catch unreachable;
|
call_args.append(self.alloc, ctx) catch unreachable;
|
||||||
@@ -7233,7 +7397,9 @@ pub const Lowering = struct {
|
|||||||
self.builder.store(slot, a);
|
self.builder.store(slot, a);
|
||||||
call_args.append(self.alloc, slot) catch unreachable;
|
call_args.append(self.alloc, slot) catch unreachable;
|
||||||
} else {
|
} else {
|
||||||
call_args.append(self.alloc, a) catch unreachable;
|
// Coerce to match declared parameter type (critical for WASM strict signatures)
|
||||||
|
const coerced = self.coerceToType(a, arg_ty, expected_ty);
|
||||||
|
call_args.append(self.alloc, coerced) catch unreachable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
||||||
@@ -8010,7 +8176,7 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
fn isInt(ty: TypeId) bool {
|
fn isInt(ty: TypeId) bool {
|
||||||
return switch (ty) {
|
return switch (ty) {
|
||||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64 => true,
|
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize => true,
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -8034,6 +8200,7 @@ pub const Lowering = struct {
|
|||||||
.s16, .u16 => 16,
|
.s16, .u16 => 16,
|
||||||
.s32, .u32 => 32,
|
.s32, .u32 => 32,
|
||||||
.s64, .u64 => 64,
|
.s64, .u64 => 64,
|
||||||
|
.usize, .isize => 0, // target-dependent — use typeBitsEx
|
||||||
.f32 => 32,
|
.f32 => 32,
|
||||||
.f64 => 64,
|
.f64 => 64,
|
||||||
else => 0,
|
else => 0,
|
||||||
@@ -8041,6 +8208,7 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn typeBitsEx(self: *Lowering, ty: TypeId) u32 {
|
fn typeBitsEx(self: *Lowering, ty: TypeId) u32 {
|
||||||
|
if (ty == .usize or ty == .isize) return @as(u32, self.module.types.pointer_size) * 8;
|
||||||
const b = typeBits(ty);
|
const b = typeBits(ty);
|
||||||
if (b > 0) return b;
|
if (b > 0) return b;
|
||||||
if (!ty.isBuiltin()) {
|
if (!ty.isBuiltin()) {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ pub fn bridgeType(ty: sx_types.Type, table: *TypeTable) TypeId {
|
|||||||
.boolean => .bool,
|
.boolean => .bool,
|
||||||
.string_type => .string,
|
.string_type => .string,
|
||||||
.any_type => .any,
|
.any_type => .any,
|
||||||
|
.usize_type => .usize,
|
||||||
|
.isize_type => .isize,
|
||||||
.enum_type => |name| resolveNamedType(name, .@"enum", table),
|
.enum_type => |name| resolveNamedType(name, .@"enum", table),
|
||||||
.struct_type => |name| resolveNamedType(name, .@"struct", table),
|
.struct_type => |name| resolveNamedType(name, .@"struct", table),
|
||||||
.union_type => |name| resolveNamedType(name, .@"union", table),
|
.union_type => |name| resolveNamedType(name, .@"union", table),
|
||||||
@@ -222,6 +224,8 @@ pub fn resolveTypePrimitive(name: []const u8) ?TypeId {
|
|||||||
if (std.mem.eql(u8, name, "Any")) return .any;
|
if (std.mem.eql(u8, name, "Any")) return .any;
|
||||||
if (std.mem.eql(u8, name, "Type")) return .any; // Type-as-value: boxed string
|
if (std.mem.eql(u8, name, "Type")) return .any; // Type-as-value: boxed string
|
||||||
if (std.mem.eql(u8, name, "noreturn")) return .noreturn;
|
if (std.mem.eql(u8, name, "noreturn")) return .noreturn;
|
||||||
|
if (std.mem.eql(u8, name, "usize")) return .usize;
|
||||||
|
if (std.mem.eql(u8, name, "isize")) return .isize;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ pub const TypeId = enum(u32) {
|
|||||||
string = 12, // [:0]u8
|
string = 12, // [:0]u8
|
||||||
any = 13,
|
any = 13,
|
||||||
noreturn = 14,
|
noreturn = 14,
|
||||||
_reserved = 15,
|
isize = 15,
|
||||||
_, // user-defined types start at 16
|
usize = 16,
|
||||||
|
_, // user-defined types start at 17
|
||||||
|
|
||||||
pub const first_user: u32 = 16;
|
pub const first_user: u32 = 17;
|
||||||
|
|
||||||
pub fn index(self: TypeId) u32 {
|
pub fn index(self: TypeId) u32 {
|
||||||
return @intFromEnum(self);
|
return @intFromEnum(self);
|
||||||
@@ -69,6 +70,8 @@ pub const TypeInfo = union(enum) {
|
|||||||
any,
|
any,
|
||||||
protocol: ProtocolInfo,
|
protocol: ProtocolInfo,
|
||||||
noreturn,
|
noreturn,
|
||||||
|
usize,
|
||||||
|
isize,
|
||||||
|
|
||||||
pub const StructInfo = struct {
|
pub const StructInfo = struct {
|
||||||
name: StringId,
|
name: StringId,
|
||||||
@@ -225,6 +228,8 @@ pub const TypeTable = struct {
|
|||||||
/// Maps TypeInfo → TypeId for dedup of structural types
|
/// Maps TypeInfo → TypeId for dedup of structural types
|
||||||
intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80),
|
intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80),
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
/// Target pointer size in bytes (4 for wasm32, 8 for 64-bit targets).
|
||||||
|
pointer_size: u8 = 8,
|
||||||
|
|
||||||
pub fn init(alloc: Allocator) TypeTable {
|
pub fn init(alloc: Allocator) TypeTable {
|
||||||
var table = TypeTable{
|
var table = TypeTable{
|
||||||
@@ -251,7 +256,8 @@ pub const TypeTable = struct {
|
|||||||
.string, // 12
|
.string, // 12
|
||||||
.any, // 13
|
.any, // 13
|
||||||
.noreturn, // 14
|
.noreturn, // 14
|
||||||
.void, // 15: reserved (placeholder)
|
.isize, // 15: isize (pointer-sized signed)
|
||||||
|
.usize, // 16: usize (pointer-sized unsigned)
|
||||||
};
|
};
|
||||||
for (&builtins) |info| {
|
for (&builtins) |info| {
|
||||||
table.infos.append(alloc, info) catch unreachable;
|
table.infos.append(alloc, info) catch unreachable;
|
||||||
@@ -402,25 +408,27 @@ pub const TypeTable = struct {
|
|||||||
/// This is the authoritative size computation used for closure env sizing and
|
/// This is the authoritative size computation used for closure env sizing and
|
||||||
/// verified against LLVMABISizeOfType.
|
/// verified against LLVMABISizeOfType.
|
||||||
pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize {
|
pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize {
|
||||||
|
const ptr_size: usize = self.pointer_size;
|
||||||
if (ty == .void) return 0;
|
if (ty == .void) return 0;
|
||||||
if (ty == .bool) return 1;
|
if (ty == .bool) return 1;
|
||||||
if (ty == .u8 or ty == .s8) return 1;
|
if (ty == .u8 or ty == .s8) return 1;
|
||||||
if (ty == .u16 or ty == .s16) return 2;
|
if (ty == .u16 or ty == .s16) return 2;
|
||||||
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||||
if (ty == .string) return 16; // {ptr, i64}
|
if (ty == .usize or ty == .isize) return ptr_size;
|
||||||
if (ty.isBuiltin()) return 8; // default for unknown builtins
|
if (ty == .string) return 16; // {ptr, i64} — always 16 (i64 alignment pads on wasm32)
|
||||||
|
if (ty.isBuiltin()) return ptr_size; // default for unknown builtins
|
||||||
const info = self.get(ty);
|
const info = self.get(ty);
|
||||||
return switch (info) {
|
return switch (info) {
|
||||||
.pointer, .many_pointer, .function => 8,
|
.pointer, .many_pointer, .function => ptr_size,
|
||||||
.slice => 16, // {ptr, i64}
|
.slice => 16, // {ptr, i64} — same layout as string
|
||||||
.closure => 16, // {fn_ptr, env_ptr}
|
.closure => 2 * ptr_size, // {fn_ptr, env_ptr}
|
||||||
.optional => |o| blk: {
|
.optional => |o| blk: {
|
||||||
const child_info = self.get(o.child);
|
const child_info = self.get(o.child);
|
||||||
if (child_info == .pointer or child_info == .many_pointer or child_info == .function)
|
if (child_info == .pointer or child_info == .many_pointer or child_info == .function)
|
||||||
break :blk 8;
|
break :blk ptr_size;
|
||||||
if (child_info == .closure)
|
if (child_info == .closure)
|
||||||
break :blk 16; // {fn_ptr, env_ptr}
|
break :blk 2 * ptr_size;
|
||||||
const cs = self.typeSizeBytes(o.child);
|
const cs = self.typeSizeBytes(o.child);
|
||||||
const ca = self.typeAlignBytes(o.child);
|
const ca = self.typeAlignBytes(o.child);
|
||||||
// { T, i1 } — i1 goes right after T, then pad to struct alignment
|
// { T, i1 } — i1 goes right after T, then pad to struct alignment
|
||||||
@@ -480,8 +488,8 @@ pub const TypeTable = struct {
|
|||||||
}
|
}
|
||||||
break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1);
|
break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1);
|
||||||
},
|
},
|
||||||
.any => 16,
|
.any => 2 * ptr_size, // {type_tag, data_ptr}
|
||||||
.protocol => 16,
|
.protocol => 2 * ptr_size, // {ctx, vtable}
|
||||||
.@"enum" => |e| {
|
.@"enum" => |e| {
|
||||||
if (e.backing_type) |bt| return self.typeSizeBytes(bt);
|
if (e.backing_type) |bt| return self.typeSizeBytes(bt);
|
||||||
return 8;
|
return 8;
|
||||||
@@ -492,22 +500,25 @@ pub const TypeTable = struct {
|
|||||||
|
|
||||||
/// Compute the ABI alignment in bytes for a type, matching LLVM's rules.
|
/// Compute the ABI alignment in bytes for a type, matching LLVM's rules.
|
||||||
pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize {
|
pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize {
|
||||||
|
const ptr_align: usize = self.pointer_size;
|
||||||
if (ty == .void) return 1;
|
if (ty == .void) return 1;
|
||||||
if (ty == .bool) return 1;
|
if (ty == .bool) return 1;
|
||||||
if (ty == .u8 or ty == .s8) return 1;
|
if (ty == .u8 or ty == .s8) return 1;
|
||||||
if (ty == .u16 or ty == .s16) return 2;
|
if (ty == .u16 or ty == .s16) return 2;
|
||||||
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||||
if (ty == .string) return 8;
|
if (ty == .usize or ty == .isize) return ptr_align;
|
||||||
if (ty.isBuiltin()) return 8;
|
if (ty == .string) return 8; // i64 drives alignment
|
||||||
|
if (ty.isBuiltin()) return ptr_align;
|
||||||
const info = self.get(ty);
|
const info = self.get(ty);
|
||||||
return switch (info) {
|
return switch (info) {
|
||||||
.pointer, .many_pointer, .function => 8,
|
.pointer, .many_pointer, .function => ptr_align,
|
||||||
.slice, .closure => 8,
|
.slice => 8, // i64 drives alignment
|
||||||
|
.closure => ptr_align, // {ptr, ptr}
|
||||||
.optional => |o| blk: {
|
.optional => |o| blk: {
|
||||||
const child_info = self.get(o.child);
|
const child_info = self.get(o.child);
|
||||||
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure)
|
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure)
|
||||||
break :blk 8;
|
break :blk ptr_align;
|
||||||
break :blk self.typeAlignBytes(o.child);
|
break :blk self.typeAlignBytes(o.child);
|
||||||
},
|
},
|
||||||
.@"struct" => |s| blk: {
|
.@"struct" => |s| blk: {
|
||||||
@@ -566,6 +577,8 @@ pub const TypeTable = struct {
|
|||||||
.string => "string",
|
.string => "string",
|
||||||
.any => "Any",
|
.any => "Any",
|
||||||
.noreturn => "noreturn",
|
.noreturn => "noreturn",
|
||||||
|
.isize => "isize",
|
||||||
|
.usize => "usize",
|
||||||
else => {
|
else => {
|
||||||
// User types — format from TypeInfo
|
// User types — format from TypeInfo
|
||||||
const info = self.get(id);
|
const info = self.get(id);
|
||||||
@@ -609,7 +622,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
|
|||||||
switch (info) {
|
switch (info) {
|
||||||
.signed => |w| h.update(&.{w}),
|
.signed => |w| h.update(&.{w}),
|
||||||
.unsigned => |w| h.update(&.{w}),
|
.unsigned => |w| h.update(&.{w}),
|
||||||
.f32, .f64, .void, .bool, .string, .any, .noreturn => {},
|
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize => {},
|
||||||
.pointer => |p| h.update(std.mem.asBytes(&p.pointee)),
|
.pointer => |p| h.update(std.mem.asBytes(&p.pointee)),
|
||||||
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
|
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
|
||||||
.slice => |s| h.update(std.mem.asBytes(&s.element)),
|
.slice => |s| h.update(std.mem.asBytes(&s.element)),
|
||||||
@@ -650,7 +663,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
|||||||
return switch (a) {
|
return switch (a) {
|
||||||
.signed => |w| w == b.signed,
|
.signed => |w| w == b.signed,
|
||||||
.unsigned => |w| w == b.unsigned,
|
.unsigned => |w| w == b.unsigned,
|
||||||
.f32, .f64, .void, .bool, .string, .any, .noreturn => true,
|
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize => true,
|
||||||
.pointer => |p| p.pointee == b.pointer.pointee,
|
.pointer => |p| p.pointee == b.pointer.pointee,
|
||||||
.many_pointer => |p| p.element == b.many_pointer.element,
|
.many_pointer => |p| p.element == b.many_pointer.element,
|
||||||
.slice => |s| s.element == b.slice.element,
|
.slice => |s| s.element == b.slice.element,
|
||||||
|
|||||||
@@ -1477,6 +1477,7 @@ pub const Server = struct {
|
|||||||
.kw_closure,
|
.kw_closure,
|
||||||
.kw_protocol,
|
.kw_protocol,
|
||||||
.kw_impl,
|
.kw_impl,
|
||||||
|
.kw_inline,
|
||||||
.hash_run,
|
.hash_run,
|
||||||
.hash_import,
|
.hash_import,
|
||||||
.hash_insert,
|
.hash_insert,
|
||||||
|
|||||||
@@ -223,6 +223,11 @@ fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Com
|
|||||||
const c_infos = try comp.collectCImportSources();
|
const c_infos = try comp.collectCImportSources();
|
||||||
if (c_infos.len == 0) return &.{};
|
if (c_infos.len == 0) return &.{};
|
||||||
|
|
||||||
|
// For Emscripten targets, use emcc to cross-compile C sources
|
||||||
|
if (comp.target_config.isEmscripten()) {
|
||||||
|
return try sx.c_import.compileCWithEmcc(allocator, io, c_infos);
|
||||||
|
}
|
||||||
|
|
||||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
|
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
|
||||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
|
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,18 @@ pub const Parser = struct {
|
|||||||
return self.parseImplBlock(start);
|
return self.parseImplBlock(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Top-level `inline if` — compile-time conditional
|
||||||
|
if (self.current.tag == .kw_inline) {
|
||||||
|
if (self.peekNext() == .kw_if) {
|
||||||
|
self.advance(); // skip 'inline'
|
||||||
|
const expr = try self.parseIfExpr();
|
||||||
|
if (expr.data == .if_expr) {
|
||||||
|
expr.data.if_expr.is_comptime = true;
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// All top-level declarations start with an identifier
|
// All top-level declarations start with an identifier
|
||||||
if (!self.isIdentLike() and self.current.tag != .kw_Self) {
|
if (!self.isIdentLike() and self.current.tag != .kw_Self) {
|
||||||
return self.fail("expected identifier at top level");
|
return self.fail("expected identifier at top level");
|
||||||
@@ -1375,6 +1387,19 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start, .{ .insert_expr = .{ .expr = inner } });
|
return try self.createNode(start, .{ .insert_expr = .{ .expr = inner } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inline if — compile-time conditional
|
||||||
|
if (self.current.tag == .kw_inline) {
|
||||||
|
if (self.peekNext() == .kw_if) {
|
||||||
|
self.advance(); // skip 'inline'
|
||||||
|
const expr = try self.parseIfExpr();
|
||||||
|
if (expr.data == .if_expr) {
|
||||||
|
expr.data.if_expr.is_comptime = true;
|
||||||
|
}
|
||||||
|
try self.expectSemicolonAfter(expr);
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Block-form if/while/for as statements — parse directly to prevent
|
// Block-form if/while/for as statements — parse directly to prevent
|
||||||
// postfix chaining (e.g. `if cond { ... }.field` being misparsed)
|
// postfix chaining (e.g. `if cond { ... }.field` being misparsed)
|
||||||
if (self.current.tag == .kw_if) {
|
if (self.current.tag == .kw_if) {
|
||||||
|
|||||||
@@ -58,6 +58,16 @@ pub const TargetConfig = struct {
|
|||||||
return self.tripleHasPrefix("wasm32", "wasm64");
|
return self.tripleHasPrefix("wasm32", "wasm64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if target triple indicates macOS/Darwin.
|
||||||
|
pub fn isMacOS(self: TargetConfig) bool {
|
||||||
|
return self.tripleContains("darwin") or self.tripleContains("macos");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if target triple indicates Linux.
|
||||||
|
pub fn isLinux(self: TargetConfig) bool {
|
||||||
|
return self.tripleContains("linux");
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if target triple indicates Emscripten (contains "emscripten").
|
/// Check if target triple indicates Emscripten (contains "emscripten").
|
||||||
pub fn isEmscripten(self: TargetConfig) bool {
|
pub fn isEmscripten(self: TargetConfig) bool {
|
||||||
return self.tripleContains("emscripten");
|
return self.tripleContains("emscripten");
|
||||||
@@ -164,11 +174,10 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
|||||||
for (target_config.lib_paths) |lp| {
|
for (target_config.lib_paths) |lp| {
|
||||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp}));
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp}));
|
||||||
}
|
}
|
||||||
for (libraries) |lib| {
|
// Skip -l flags for Emscripten: libraries like SDL3 are provided via
|
||||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib}));
|
// -sUSE_SDL=3, not -lSDL3. User provides everything via --lflags.
|
||||||
}
|
|
||||||
|
|
||||||
// Extra linker flags (e.g. -sUSE_SDL=2, -sUSE_WEBGL2=1, --preload-file)
|
// Extra linker flags (e.g. -sUSE_SDL=3, -sUSE_WEBGL2=1, --preload-file)
|
||||||
for (target_config.extra_link_flags) |flag| {
|
for (target_config.extra_link_flags) |flag| {
|
||||||
try argv.append(allocator, flag);
|
try argv.append(allocator, flag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pub const Tag = enum {
|
|||||||
kw_protocol, // protocol
|
kw_protocol, // protocol
|
||||||
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)
|
||||||
|
|
||||||
// Symbols
|
// Symbols
|
||||||
colon, // :
|
colon, // :
|
||||||
@@ -224,6 +225,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
|||||||
.{ "protocol", .kw_protocol },
|
.{ "protocol", .kw_protocol },
|
||||||
.{ "impl", .kw_impl },
|
.{ "impl", .kw_impl },
|
||||||
.{ "Self", .kw_Self },
|
.{ "Self", .kw_Self },
|
||||||
|
.{ "inline", .kw_inline },
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn getKeyword(bytes: []const u8) ?Tag {
|
pub fn getKeyword(bytes: []const u8) ?Tag {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ pub const Type = union(enum) {
|
|||||||
function_type: FunctionTypeInfo,
|
function_type: FunctionTypeInfo,
|
||||||
closure_type: ClosureTypeInfo,
|
closure_type: ClosureTypeInfo,
|
||||||
any_type,
|
any_type,
|
||||||
|
usize_type,
|
||||||
|
isize_type,
|
||||||
optional_type: OptionalTypeInfo,
|
optional_type: OptionalTypeInfo,
|
||||||
meta_type: MetaTypeInfo,
|
meta_type: MetaTypeInfo,
|
||||||
tuple_type: TupleTypeInfo,
|
tuple_type: TupleTypeInfo,
|
||||||
@@ -82,7 +84,7 @@ pub const Type = union(enum) {
|
|||||||
return switch (self) {
|
return switch (self) {
|
||||||
.signed => |w| w == other.signed,
|
.signed => |w| w == other.signed,
|
||||||
.unsigned => |w| w == other.unsigned,
|
.unsigned => |w| w == other.unsigned,
|
||||||
.f32, .f64, .void_type, .boolean, .string_type, .any_type => true,
|
.f32, .f64, .void_type, .boolean, .string_type, .any_type, .usize_type, .isize_type => true,
|
||||||
.enum_type => |n| std.mem.eql(u8, n, other.enum_type),
|
.enum_type => |n| std.mem.eql(u8, n, other.enum_type),
|
||||||
.struct_type => |n| std.mem.eql(u8, n, other.struct_type),
|
.struct_type => |n| std.mem.eql(u8, n, other.struct_type),
|
||||||
.union_type => |n| std.mem.eql(u8, n, other.union_type),
|
.union_type => |n| std.mem.eql(u8, n, other.union_type),
|
||||||
@@ -149,12 +151,17 @@ pub const Type = union(enum) {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
'u' => {
|
'u' => {
|
||||||
|
if (std.mem.eql(u8, name, "usize")) return .usize_type;
|
||||||
if (name.len >= 2) {
|
if (name.len >= 2) {
|
||||||
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
|
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
|
||||||
if (width >= 1 and width <= 64) return Type.u(width);
|
if (width >= 1 and width <= 64) return Type.u(width);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
'i' => {
|
||||||
|
if (std.mem.eql(u8, name, "isize")) return .isize_type;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null,
|
'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null,
|
||||||
'f' => {
|
'f' => {
|
||||||
if (std.mem.eql(u8, name, "f32")) return .f32;
|
if (std.mem.eql(u8, name, "f32")) return .f32;
|
||||||
@@ -223,6 +230,8 @@ pub const Type = union(enum) {
|
|||||||
.boolean => "bool",
|
.boolean => "bool",
|
||||||
.string_type => "string",
|
.string_type => "string",
|
||||||
.void_type => "void",
|
.void_type => "void",
|
||||||
|
.usize_type => "usize",
|
||||||
|
.isize_type => "isize",
|
||||||
.struct_type => |n| n,
|
.struct_type => |n| n,
|
||||||
.enum_type => |n| n,
|
.enum_type => |n| n,
|
||||||
.union_type => |n| n,
|
.union_type => |n| n,
|
||||||
@@ -544,6 +553,8 @@ pub const Type = union(enum) {
|
|||||||
.string_type => "string",
|
.string_type => "string",
|
||||||
.void_type => "void",
|
.void_type => "void",
|
||||||
.any_type => "Any",
|
.any_type => "Any",
|
||||||
|
.usize_type => "usize",
|
||||||
|
.isize_type => "isize",
|
||||||
.enum_type => |name| name,
|
.enum_type => |name| name,
|
||||||
.struct_type => |name| name,
|
.struct_type => |name| name,
|
||||||
.union_type => |name| name,
|
.union_type => |name| name,
|
||||||
|
|||||||
@@ -592,4 +592,14 @@ opt-if2: 10.000000
|
|||||||
opt-if3: 10.000000
|
opt-if3: 10.000000
|
||||||
opt-if4: 0.000000
|
opt-if4: 0.000000
|
||||||
opt-if5: 42.000000
|
opt-if5: 42.000000
|
||||||
|
usize: 42
|
||||||
|
isize: -7
|
||||||
|
usize+8: 50
|
||||||
|
s32->usize: 10
|
||||||
|
usize->s64: 42
|
||||||
|
=== inline if ===
|
||||||
|
64-bit
|
||||||
|
not wasm
|
||||||
|
known os
|
||||||
|
desktop 64-bit
|
||||||
=== DONE ===
|
=== DONE ===
|
||||||
|
|||||||
Reference in New Issue
Block a user