asm...
This commit is contained in:
@@ -208,6 +208,7 @@ pub const IfExpr = struct {
|
||||
then_branch: *Node,
|
||||
else_branch: ?*Node,
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
pub fn writeCObjectFiles(
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
@@ -116,9 +116,14 @@ pub const Compilation = struct {
|
||||
pub fn lowerToIR(self: *Compilation) ir.Module {
|
||||
const root = self.resolved_root orelse self.root orelse return 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);
|
||||
lowering.main_file = self.file_path;
|
||||
lowering.resolved_root = root;
|
||||
lowering.target_config = self.target_config;
|
||||
lowering.lowerRoot(root);
|
||||
return module;
|
||||
}
|
||||
|
||||
@@ -2103,8 +2103,9 @@ pub const LLVMEmitter = struct {
|
||||
}
|
||||
|
||||
// int→int: SExt, ZExt, or Trunc
|
||||
const from_bits = intBits(from);
|
||||
const to_bits = intBits(to);
|
||||
const ptr_bits: u32 = @as(u32, self.ir_mod.types.pointer_size) * 8;
|
||||
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) {
|
||||
return if (isSignedType(from))
|
||||
c.LLVMBuildSExt(self.builder, operand, to_ty, "sext")
|
||||
@@ -2539,6 +2540,7 @@ pub const LLVMEmitter = struct {
|
||||
.string => self.getStringStructType(),
|
||||
.any => self.getAnyStructType(),
|
||||
.noreturn => self.cached_void,
|
||||
.isize, .usize => if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64,
|
||||
else => self.toLLVMTypeInfo(ty),
|
||||
};
|
||||
}
|
||||
@@ -2665,6 +2667,7 @@ pub const LLVMEmitter = struct {
|
||||
// For now, use opaque 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;
|
||||
}
|
||||
|
||||
// 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 (c.LLVMGetTypeKind(llvm_ty) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(llvm_ty) == 64) {
|
||||
// s64/u64 in extern decls → i32 on wasm32 (matches C's size_t, ssize_t, etc.)
|
||||
return self.cached_i32;
|
||||
}
|
||||
if (ir_ty == .usize or ir_ty == .isize) return self.cached_i32;
|
||||
return llvm_ty;
|
||||
}
|
||||
|
||||
@@ -3075,7 +3077,7 @@ fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool {
|
||||
|
||||
fn isSignedType(ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64 => true,
|
||||
.s8, .s16, .s32, .s64, .isize => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
@@ -3095,6 +3097,7 @@ fn intBits(ty: TypeId) u32 {
|
||||
.s32, .u32 => 32,
|
||||
.s64, .u64 => 64,
|
||||
.bool => 1,
|
||||
.usize, .isize => 0, // target-dependent — caller must query pointer_size
|
||||
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
|
||||
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
|
||||
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 {
|
||||
value: *const Node,
|
||||
@@ -165,12 +172,68 @@ pub const Lowering = struct {
|
||||
};
|
||||
// Pass 1: scan — register all function ASTs, struct types, extern stubs
|
||||
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)
|
||||
self.lowerMainAndComptime(decls);
|
||||
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
|
||||
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.
|
||||
/// At this point, main is fully lowered and all types are in the TypeTable.
|
||||
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) {
|
||||
.undef_literal => .zeroinit,
|
||||
.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) },
|
||||
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).
|
||||
fn lowerBlockValue(self: *Lowering, node: *const Node) ?Ref {
|
||||
// Set force_block_value so nested if-else expressions produce values
|
||||
@@ -1845,6 +1919,19 @@ pub const Lowering = struct {
|
||||
// ── Control flow ────────────────────────────────────────────────
|
||||
|
||||
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
|
||||
if (self.tryConstBoolCondition(ie.condition)) |is_true| {
|
||||
if (is_true) {
|
||||
@@ -1999,6 +2086,46 @@ pub const Lowering = struct {
|
||||
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 {
|
||||
const header_bb = self.freshBlock("while.hdr");
|
||||
const body_bb = self.freshBlock("while.body");
|
||||
@@ -2857,6 +2984,10 @@ pub const Lowering = struct {
|
||||
const info = self.module.types.get(obj_ty);
|
||||
switch (info) {
|
||||
.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
|
||||
for (u.fields, 0..) |f, i| {
|
||||
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)
|
||||
const obj_ty = self.inferExprType(fa.object);
|
||||
const obj = self.lowerExpr(fa.object);
|
||||
// For ptr.*.method(): pass the pointer directly instead of loading + re-addressing.
|
||||
// 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
|
||||
if (!obj_ty.isBuiltin()) {
|
||||
@@ -3781,7 +3923,7 @@ pub const Lowering = struct {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
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);
|
||||
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 ret_ty = func.ret;
|
||||
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
|
||||
// (module.addFunction), invalidating func pointer.
|
||||
// Use pre-extracted params/ret_ty instead of func.* after this.
|
||||
@@ -4005,6 +4147,14 @@ pub const Lowering = struct {
|
||||
if (!tt.isBuiltin()) {
|
||||
const tti = self.module.types.get(tt);
|
||||
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;
|
||||
} else null;
|
||||
@@ -4031,6 +4181,14 @@ pub const Lowering = struct {
|
||||
if (!tt.isBuiltin()) {
|
||||
const tti = self.module.types.get(tt);
|
||||
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
|
||||
@@ -4388,8 +4546,7 @@ pub const Lowering = struct {
|
||||
offset = (offset + field_align - 1) & ~(field_align - 1);
|
||||
offset += field_size;
|
||||
}
|
||||
// Align total to struct alignment (max field alignment, minimum 8)
|
||||
if (max_align < 8) max_align = 8;
|
||||
// Align total to max field alignment (matches LLVM's struct alignment)
|
||||
return (offset + max_align - 1) & ~(max_align - 1);
|
||||
}
|
||||
|
||||
@@ -5716,6 +5873,8 @@ pub const Lowering = struct {
|
||||
if (ty == .void) return "void";
|
||||
if (ty == .string) return "string";
|
||||
if (ty == .any) return "Any";
|
||||
if (ty == .usize) return "usize";
|
||||
if (ty == .isize) return "isize";
|
||||
|
||||
const info = self.module.types.get(ty);
|
||||
return switch (info) {
|
||||
@@ -5880,6 +6039,8 @@ pub const Lowering = struct {
|
||||
if (ty == .void) return "void";
|
||||
if (ty == .string) return "string";
|
||||
if (ty == .any) return "Any";
|
||||
if (ty == .usize) return "usize";
|
||||
if (ty == .isize) return "isize";
|
||||
|
||||
const info = self.module.types.get(ty);
|
||||
return switch (info) {
|
||||
@@ -5932,6 +6093,8 @@ pub const Lowering = struct {
|
||||
tags.append(self.alloc, TypeId.u16.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.u32.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;
|
||||
}
|
||||
if (std.mem.eql(u8, name, "float")) {
|
||||
@@ -7220,6 +7383,7 @@ pub const Lowering = struct {
|
||||
// Build call args: ctx + user args
|
||||
// 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.
|
||||
// Also coerce argument types to match declared param types (e.g., s64 → s32).
|
||||
var call_args = std.ArrayList(Ref).empty;
|
||||
defer call_args.deinit(self.alloc);
|
||||
call_args.append(self.alloc, ctx) catch unreachable;
|
||||
@@ -7233,7 +7397,9 @@ pub const Lowering = struct {
|
||||
self.builder.store(slot, a);
|
||||
call_args.append(self.alloc, slot) catch unreachable;
|
||||
} 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;
|
||||
@@ -8010,7 +8176,7 @@ pub const Lowering = struct {
|
||||
|
||||
fn isInt(ty: TypeId) bool {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -8034,6 +8200,7 @@ pub const Lowering = struct {
|
||||
.s16, .u16 => 16,
|
||||
.s32, .u32 => 32,
|
||||
.s64, .u64 => 64,
|
||||
.usize, .isize => 0, // target-dependent — use typeBitsEx
|
||||
.f32 => 32,
|
||||
.f64 => 64,
|
||||
else => 0,
|
||||
@@ -8041,6 +8208,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
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);
|
||||
if (b > 0) return b;
|
||||
if (!ty.isBuiltin()) {
|
||||
|
||||
@@ -65,6 +65,8 @@ pub fn bridgeType(ty: sx_types.Type, table: *TypeTable) TypeId {
|
||||
.boolean => .bool,
|
||||
.string_type => .string,
|
||||
.any_type => .any,
|
||||
.usize_type => .usize,
|
||||
.isize_type => .isize,
|
||||
.enum_type => |name| resolveNamedType(name, .@"enum", table),
|
||||
.struct_type => |name| resolveNamedType(name, .@"struct", 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, "Type")) return .any; // Type-as-value: boxed string
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,11 @@ pub const TypeId = enum(u32) {
|
||||
string = 12, // [:0]u8
|
||||
any = 13,
|
||||
noreturn = 14,
|
||||
_reserved = 15,
|
||||
_, // user-defined types start at 16
|
||||
isize = 15,
|
||||
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 {
|
||||
return @intFromEnum(self);
|
||||
@@ -69,6 +70,8 @@ pub const TypeInfo = union(enum) {
|
||||
any,
|
||||
protocol: ProtocolInfo,
|
||||
noreturn,
|
||||
usize,
|
||||
isize,
|
||||
|
||||
pub const StructInfo = struct {
|
||||
name: StringId,
|
||||
@@ -225,6 +228,8 @@ pub const TypeTable = struct {
|
||||
/// Maps TypeInfo → TypeId for dedup of structural types
|
||||
intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80),
|
||||
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 {
|
||||
var table = TypeTable{
|
||||
@@ -251,7 +256,8 @@ pub const TypeTable = struct {
|
||||
.string, // 12
|
||||
.any, // 13
|
||||
.noreturn, // 14
|
||||
.void, // 15: reserved (placeholder)
|
||||
.isize, // 15: isize (pointer-sized signed)
|
||||
.usize, // 16: usize (pointer-sized unsigned)
|
||||
};
|
||||
for (&builtins) |info| {
|
||||
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
|
||||
/// verified against LLVMABISizeOfType.
|
||||
pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize {
|
||||
const ptr_size: usize = self.pointer_size;
|
||||
if (ty == .void) return 0;
|
||||
if (ty == .bool) return 1;
|
||||
if (ty == .u8 or ty == .s8) return 1;
|
||||
if (ty == .u16 or ty == .s16) return 2;
|
||||
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .string) return 16; // {ptr, i64}
|
||||
if (ty.isBuiltin()) return 8; // default for unknown builtins
|
||||
if (ty == .usize or ty == .isize) return ptr_size;
|
||||
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);
|
||||
return switch (info) {
|
||||
.pointer, .many_pointer, .function => 8,
|
||||
.slice => 16, // {ptr, i64}
|
||||
.closure => 16, // {fn_ptr, env_ptr}
|
||||
.pointer, .many_pointer, .function => ptr_size,
|
||||
.slice => 16, // {ptr, i64} — same layout as string
|
||||
.closure => 2 * ptr_size, // {fn_ptr, env_ptr}
|
||||
.optional => |o| blk: {
|
||||
const child_info = self.get(o.child);
|
||||
if (child_info == .pointer or child_info == .many_pointer or child_info == .function)
|
||||
break :blk 8;
|
||||
break :blk ptr_size;
|
||||
if (child_info == .closure)
|
||||
break :blk 16; // {fn_ptr, env_ptr}
|
||||
break :blk 2 * ptr_size;
|
||||
const cs = self.typeSizeBytes(o.child);
|
||||
const ca = self.typeAlignBytes(o.child);
|
||||
// { 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);
|
||||
},
|
||||
.any => 16,
|
||||
.protocol => 16,
|
||||
.any => 2 * ptr_size, // {type_tag, data_ptr}
|
||||
.protocol => 2 * ptr_size, // {ctx, vtable}
|
||||
.@"enum" => |e| {
|
||||
if (e.backing_type) |bt| return self.typeSizeBytes(bt);
|
||||
return 8;
|
||||
@@ -492,22 +500,25 @@ pub const TypeTable = struct {
|
||||
|
||||
/// Compute the ABI alignment in bytes for a type, matching LLVM's rules.
|
||||
pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize {
|
||||
const ptr_align: usize = self.pointer_size;
|
||||
if (ty == .void) return 1;
|
||||
if (ty == .bool) return 1;
|
||||
if (ty == .u8 or ty == .s8) return 1;
|
||||
if (ty == .u16 or ty == .s16) return 2;
|
||||
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||
if (ty == .string) return 8;
|
||||
if (ty.isBuiltin()) return 8;
|
||||
if (ty == .usize or ty == .isize) return ptr_align;
|
||||
if (ty == .string) return 8; // i64 drives alignment
|
||||
if (ty.isBuiltin()) return ptr_align;
|
||||
const info = self.get(ty);
|
||||
return switch (info) {
|
||||
.pointer, .many_pointer, .function => 8,
|
||||
.slice, .closure => 8,
|
||||
.pointer, .many_pointer, .function => ptr_align,
|
||||
.slice => 8, // i64 drives alignment
|
||||
.closure => ptr_align, // {ptr, ptr}
|
||||
.optional => |o| blk: {
|
||||
const child_info = self.get(o.child);
|
||||
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);
|
||||
},
|
||||
.@"struct" => |s| blk: {
|
||||
@@ -566,6 +577,8 @@ pub const TypeTable = struct {
|
||||
.string => "string",
|
||||
.any => "Any",
|
||||
.noreturn => "noreturn",
|
||||
.isize => "isize",
|
||||
.usize => "usize",
|
||||
else => {
|
||||
// User types — format from TypeInfo
|
||||
const info = self.get(id);
|
||||
@@ -609,7 +622,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
|
||||
switch (info) {
|
||||
.signed => |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)),
|
||||
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
|
||||
.slice => |s| h.update(std.mem.asBytes(&s.element)),
|
||||
@@ -650,7 +663,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
||||
return switch (a) {
|
||||
.signed => |w| w == b.signed,
|
||||
.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,
|
||||
.many_pointer => |p| p.element == b.many_pointer.element,
|
||||
.slice => |s| s.element == b.slice.element,
|
||||
|
||||
@@ -1477,6 +1477,7 @@ pub const Server = struct {
|
||||
.kw_closure,
|
||||
.kw_protocol,
|
||||
.kw_impl,
|
||||
.kw_inline,
|
||||
.hash_run,
|
||||
.hash_import,
|
||||
.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();
|
||||
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);
|
||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,18 @@ pub const Parser = struct {
|
||||
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
|
||||
if (!self.isIdentLike() and self.current.tag != .kw_Self) {
|
||||
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 } });
|
||||
}
|
||||
|
||||
// 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
|
||||
// postfix chaining (e.g. `if cond { ... }.field` being misparsed)
|
||||
if (self.current.tag == .kw_if) {
|
||||
|
||||
@@ -58,6 +58,16 @@ pub const TargetConfig = struct {
|
||||
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").
|
||||
pub fn isEmscripten(self: TargetConfig) bool {
|
||||
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| {
|
||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp}));
|
||||
}
|
||||
for (libraries) |lib| {
|
||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib}));
|
||||
}
|
||||
// Skip -l flags for Emscripten: libraries like SDL3 are provided via
|
||||
// -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| {
|
||||
try argv.append(allocator, flag);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ pub const Tag = enum {
|
||||
kw_protocol, // protocol
|
||||
kw_impl, // impl
|
||||
kw_Self, // Self (in protocol declarations)
|
||||
kw_inline, // inline (compile-time if/for/while)
|
||||
|
||||
// Symbols
|
||||
colon, // :
|
||||
@@ -224,6 +225,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
||||
.{ "protocol", .kw_protocol },
|
||||
.{ "impl", .kw_impl },
|
||||
.{ "Self", .kw_Self },
|
||||
.{ "inline", .kw_inline },
|
||||
});
|
||||
|
||||
pub fn getKeyword(bytes: []const u8) ?Tag {
|
||||
|
||||
@@ -24,6 +24,8 @@ pub const Type = union(enum) {
|
||||
function_type: FunctionTypeInfo,
|
||||
closure_type: ClosureTypeInfo,
|
||||
any_type,
|
||||
usize_type,
|
||||
isize_type,
|
||||
optional_type: OptionalTypeInfo,
|
||||
meta_type: MetaTypeInfo,
|
||||
tuple_type: TupleTypeInfo,
|
||||
@@ -82,7 +84,7 @@ pub const Type = union(enum) {
|
||||
return switch (self) {
|
||||
.signed => |w| w == other.signed,
|
||||
.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),
|
||||
.struct_type => |n| std.mem.eql(u8, n, other.struct_type),
|
||||
.union_type => |n| std.mem.eql(u8, n, other.union_type),
|
||||
@@ -149,12 +151,17 @@ pub const Type = union(enum) {
|
||||
return null;
|
||||
},
|
||||
'u' => {
|
||||
if (std.mem.eql(u8, name, "usize")) return .usize_type;
|
||||
if (name.len >= 2) {
|
||||
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
|
||||
if (width >= 1 and width <= 64) return Type.u(width);
|
||||
}
|
||||
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,
|
||||
'f' => {
|
||||
if (std.mem.eql(u8, name, "f32")) return .f32;
|
||||
@@ -223,6 +230,8 @@ pub const Type = union(enum) {
|
||||
.boolean => "bool",
|
||||
.string_type => "string",
|
||||
.void_type => "void",
|
||||
.usize_type => "usize",
|
||||
.isize_type => "isize",
|
||||
.struct_type => |n| n,
|
||||
.enum_type => |n| n,
|
||||
.union_type => |n| n,
|
||||
@@ -544,6 +553,8 @@ pub const Type = union(enum) {
|
||||
.string_type => "string",
|
||||
.void_type => "void",
|
||||
.any_type => "Any",
|
||||
.usize_type => "usize",
|
||||
.isize_type => "isize",
|
||||
.enum_type => |name| name,
|
||||
.struct_type => |name| name,
|
||||
.union_type => |name| name,
|
||||
|
||||
Reference in New Issue
Block a user