sm
This commit is contained in:
@@ -86,6 +86,9 @@ pub const LLVMEmitter = struct {
|
||||
// Target configuration (stored for ABI decisions during emission)
|
||||
target_config: TargetConfig,
|
||||
|
||||
// Build configuration accumulated from #run blocks
|
||||
build_config: interp_mod.BuildConfig,
|
||||
|
||||
const PendingPhi = struct {
|
||||
phi: c.LLVMValueRef,
|
||||
block_id: BlockId, // the block this phi belongs to
|
||||
@@ -158,10 +161,12 @@ pub const LLVMEmitter = struct {
|
||||
.closure_struct_type = null,
|
||||
.field_name_arrays = std.AutoHashMap(u32, c.LLVMValueRef).init(alloc),
|
||||
.target_config = target_config,
|
||||
.build_config = .{},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *LLVMEmitter) void {
|
||||
self.build_config.deinit(self.alloc);
|
||||
self.ref_map.deinit();
|
||||
self.func_map.deinit();
|
||||
self.field_name_arrays.deinit();
|
||||
@@ -199,9 +204,9 @@ pub const LLVMEmitter = struct {
|
||||
|
||||
/// Compare IR typeSizeBytes against LLVMABISizeOfType for all user-defined types.
|
||||
fn verifySizes(self: *LLVMEmitter) void {
|
||||
// Skip for WASM: wasm32 has 4-byte pointers vs IR's assumed 8-byte,
|
||||
// Skip for wasm32: 4-byte pointers vs IR's assumed 8-byte,
|
||||
// so struct sizes will differ. LLVM handles emission correctly.
|
||||
if (self.target_config.isWasm()) return;
|
||||
if (self.target_config.isWasm32()) return;
|
||||
const dl = c.LLVMGetModuleDataLayout(self.llvm_module);
|
||||
if (dl == null) return;
|
||||
const type_count = self.ir_mod.types.infos.items.len;
|
||||
@@ -241,6 +246,7 @@ pub const LLVMEmitter = struct {
|
||||
// Run the side-effect function via interpreter
|
||||
const func_id = ir_inst.FuncId.fromIndex(@intCast(i));
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
_ = interp_inst.call(func_id, &.{}) catch {};
|
||||
// Write comptime output to stderr (same as old comptime VM)
|
||||
if (interp_inst.output.items.len > 0) {
|
||||
@@ -263,6 +269,7 @@ pub const LLVMEmitter = struct {
|
||||
// Evaluate comptime initializer if present
|
||||
if (global.comptime_func) |func_id| {
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
const result = interp_inst.call(func_id, &.{}) catch .void_val;
|
||||
const init_val = self.valueToLLVMConst(result, llvm_ty);
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
@@ -793,6 +800,7 @@ pub const LLVMEmitter = struct {
|
||||
const callee_func = &self.ir_mod.functions.items[call_op.callee.index()];
|
||||
if (callee_func.is_comptime and call_op.args.len == 0) {
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
defer interp_inst.deinit();
|
||||
if (interp_inst.call(call_op.callee, &.{})) |result| {
|
||||
if (result.asInt()) |v| {
|
||||
@@ -1427,7 +1435,7 @@ pub const LLVMEmitter = struct {
|
||||
const raw_ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str.ptr");
|
||||
const str_len = c.LLVMBuildExtractValue(self.builder, str_val, 1, "str.len");
|
||||
// On wasm32, count param is i32 (size_t)
|
||||
const count = if (self.target_config.isWasm())
|
||||
const count = if (self.target_config.isWasm32())
|
||||
c.LLVMBuildTrunc(self.builder, str_len, self.cached_i32, "len.tr")
|
||||
else
|
||||
str_len;
|
||||
@@ -2132,9 +2140,9 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMAddFunction(self.llvm_module, "free", fn_ty);
|
||||
}
|
||||
|
||||
/// Returns the LLVM type for C `size_t`: i32 on wasm32, i64 on 64-bit targets.
|
||||
/// Returns the LLVM type for C `size_t`: i32 on wasm32, i64 on 64-bit targets (including wasm64).
|
||||
fn sizeType(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
return if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64;
|
||||
return if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64;
|
||||
}
|
||||
|
||||
fn getMallocType(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
@@ -2540,7 +2548,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,
|
||||
.isize, .usize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
||||
else => self.toLLVMTypeInfo(ty),
|
||||
};
|
||||
}
|
||||
@@ -2667,7 +2675,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,
|
||||
.usize, .isize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2690,7 +2698,7 @@ pub const LLVMEmitter = struct {
|
||||
// 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.isWasm32()) {
|
||||
if (ir_ty == .usize or ir_ty == .isize) return self.cached_i32;
|
||||
return llvm_ty;
|
||||
}
|
||||
@@ -3007,6 +3015,13 @@ pub const LLVMEmitter = struct {
|
||||
return self.emitToFile(output_path, c.LLVMAssemblyFile);
|
||||
}
|
||||
|
||||
/// Emit the module as LLVM bitcode to disk (for emcc to recompile with a newer LLVM).
|
||||
pub fn emitBitcode(self: *LLVMEmitter, output_path: [*:0]const u8) !void {
|
||||
if (c.LLVMWriteBitcodeToFile(self.llvm_module, output_path) != 0) {
|
||||
return error.EmitFailed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Dump the LLVM IR to a file for debugging.
|
||||
pub fn dumpIRToFile(self: *LLVMEmitter, path: [*:0]const u8) void {
|
||||
_ = c.LLVMPrintModuleToFile(self.llvm_module, path, null);
|
||||
|
||||
@@ -302,6 +302,9 @@ pub const BuiltinId = enum(u16) {
|
||||
type_of,
|
||||
alloc,
|
||||
dealloc,
|
||||
build_options,
|
||||
build_options_add_link_flag,
|
||||
build_options_set_output_path,
|
||||
};
|
||||
|
||||
pub const ProtocolCall = struct {
|
||||
|
||||
@@ -106,6 +106,18 @@ pub const InterpError = error{
|
||||
Unreachable,
|
||||
};
|
||||
|
||||
// ── BuildConfig ─────────────────────────────────────────────────────────
|
||||
// Mutable build configuration accumulated by #run blocks via BuildOptions methods.
|
||||
|
||||
pub const BuildConfig = struct {
|
||||
link_flags: std.ArrayList([]const u8) = .empty,
|
||||
output_path: ?[]const u8 = null,
|
||||
|
||||
pub fn deinit(self: *BuildConfig, alloc: Allocator) void {
|
||||
self.link_flags.deinit(alloc);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Interpreter ─────────────────────────────────────────────────────────
|
||||
|
||||
pub const Interpreter = struct {
|
||||
@@ -121,6 +133,9 @@ pub const Interpreter = struct {
|
||||
// Global values: evaluated comptime globals, indexed by GlobalId
|
||||
global_values: std.AutoHashMap(u32, Value),
|
||||
|
||||
// Mutable build configuration — set by LLVMEmitter, written by #run blocks
|
||||
build_config: ?*BuildConfig = null,
|
||||
|
||||
pub fn init(module: *const Module, alloc: Allocator) Interpreter {
|
||||
return .{
|
||||
.module = module,
|
||||
@@ -1242,6 +1257,30 @@ pub const Interpreter = struct {
|
||||
const f = val.asFloat() orelse return error.TypeError;
|
||||
return .{ .value = .{ .float = @floor(f) } };
|
||||
},
|
||||
.build_options => {
|
||||
// Returns a void sentinel — the "handle" to BuildConfig
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.build_options_add_link_flag => {
|
||||
// args: [opts_handle, flag_string]
|
||||
const str_val = frame.getRef(bi.args[1]);
|
||||
if (str_val.asString(self)) |s| {
|
||||
if (self.build_config) |bc| {
|
||||
bc.link_flags.append(self.alloc, self.alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
|
||||
}
|
||||
}
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.build_options_set_output_path => {
|
||||
// args: [opts_handle, path_string]
|
||||
const str_val = frame.getRef(bi.args[1]);
|
||||
if (str_val.asString(self)) |s| {
|
||||
if (self.build_config) |bc| {
|
||||
bc.output_path = self.alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
||||
}
|
||||
}
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.cast, .type_of, .alloc, .dealloc => {
|
||||
return error.CannotEvalComptime;
|
||||
},
|
||||
|
||||
@@ -204,13 +204,15 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// ARCH: Architecture enum { aarch64; x86_64; wasm32; unknown; }
|
||||
// ARCH: Architecture enum { aarch64; x86_64; wasm32; wasm64; 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())
|
||||
const tag: u32 = if (tc.isWasm32())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "wasm32")
|
||||
else if (tc.isWasm64())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "wasm64")
|
||||
else if (tc.isAarch64())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "aarch64")
|
||||
else if (tc.isX86_64())
|
||||
@@ -221,8 +223,8 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// POINTER_SIZE: s64 (4 for wasm, 8 otherwise)
|
||||
const ptr_size: i64 = if (tc.isWasm()) 4 else 8;
|
||||
// POINTER_SIZE: s64 (4 for wasm32, 8 for wasm64 and other 64-bit targets)
|
||||
const ptr_size: i64 = if (tc.isWasm32()) 4 else 8;
|
||||
self.comptime_constants.put("POINTER_SIZE", .{ .int_val = ptr_size }) catch {};
|
||||
}
|
||||
|
||||
@@ -2126,6 +2128,52 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a compile-time match expression for `inline if ... == { case ... }`.
|
||||
/// Returns the body of the matching arm, or null if the match can't be resolved.
|
||||
fn evalComptimeMatch(self: *Lowering, me: *const ast.MatchExpr) ?*const Node {
|
||||
// Subject must be a comptime constant identifier
|
||||
const name = switch (me.subject.data) {
|
||||
.identifier => |id| id.name,
|
||||
else => return null,
|
||||
};
|
||||
const cv = self.comptime_constants.get(name) orelse return null;
|
||||
|
||||
switch (cv) {
|
||||
.enum_tag => |et| {
|
||||
const enum_info = self.module.types.get(et.ty);
|
||||
if (enum_info != .@"enum") return null;
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) continue; // default arm
|
||||
const variant_name = switch (arm.pattern.?.data) {
|
||||
.enum_literal => |el| el.name,
|
||||
else => continue,
|
||||
};
|
||||
const variant_idx = self.findVariantIndex(enum_info.@"enum".variants, variant_name);
|
||||
if (et.tag == variant_idx) return arm.body;
|
||||
}
|
||||
// No match — try default arm
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) return arm.body;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
.int_val => |iv| {
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) continue;
|
||||
const rhs_val: i64 = switch (arm.pattern.?.data) {
|
||||
.int_literal => |il| il.value,
|
||||
else => continue,
|
||||
};
|
||||
if (iv == rhs_val) return arm.body;
|
||||
}
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) return arm.body;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref {
|
||||
const header_bb = self.freshBlock("while.hdr");
|
||||
const body_bb = self.freshBlock("while.body");
|
||||
@@ -2240,6 +2288,14 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn lowerMatch(self: *Lowering, me: *const ast.MatchExpr) Ref {
|
||||
// inline if match: evaluate at compile time, only lower the matching arm
|
||||
if (me.is_comptime) {
|
||||
if (self.evalComptimeMatch(me)) |arm_body| {
|
||||
return self.lowerInlineBranch(arm_body);
|
||||
}
|
||||
// Couldn't evaluate — fall through to runtime
|
||||
}
|
||||
|
||||
const is_type_match = isTypeCategoryMatch(me);
|
||||
const subject = self.lowerExpr(me.subject);
|
||||
|
||||
@@ -3904,6 +3960,15 @@ pub const Lowering = struct {
|
||||
// Try to resolve the method by struct type name
|
||||
const struct_name = self.getStructTypeName(obj_ty);
|
||||
if (struct_name) |sname| {
|
||||
// Intercept BuildOptions compiler builtins
|
||||
if (std.mem.eql(u8, sname, "BuildOptions")) {
|
||||
if (std.mem.eql(u8, fa.field, "add_link_flag")) {
|
||||
return self.builder.callBuiltin(.build_options_add_link_flag, method_args.items, .void);
|
||||
} else if (std.mem.eql(u8, fa.field, "set_output_path")) {
|
||||
return self.builder.callBuiltin(.build_options_set_output_path, method_args.items, .void);
|
||||
}
|
||||
}
|
||||
|
||||
// Try direct qualified name: StructName.method
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch fa.field;
|
||||
|
||||
@@ -7530,6 +7595,12 @@ pub const Lowering = struct {
|
||||
const oi = self.module.types.get(obj_ty);
|
||||
if (oi == .@"struct") {
|
||||
const struct_name = self.module.types.getString(oi.@"struct".name);
|
||||
// Intercept BuildOptions compiler builtins
|
||||
if (std.mem.eql(u8, struct_name, "BuildOptions")) {
|
||||
if (std.mem.eql(u8, cfa.field, "add_link_flag") or std.mem.eql(u8, cfa.field, "set_output_path")) {
|
||||
return .void;
|
||||
}
|
||||
}
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ struct_name, cfa.field }) catch cfa.field;
|
||||
if (self.resolveFuncByName(qualified)) |fid| {
|
||||
return self.module.functions.items[@intFromEnum(fid)].ret;
|
||||
|
||||
Reference in New Issue
Block a user