This commit is contained in:
agra
2026-03-02 09:49:43 +02:00
parent f763765ea2
commit ba9c4d69ce
6 changed files with 460 additions and 103 deletions

View File

@@ -83,6 +83,9 @@ pub const LLVMEmitter = struct {
// Cached field name arrays for reflection (TypeId → LLVM global)
field_name_arrays: std.AutoHashMap(u32, c.LLVMValueRef),
// Target configuration (stored for ABI decisions during emission)
target_config: TargetConfig,
const PendingPhi = struct {
phi: c.LLVMValueRef,
block_id: BlockId, // the block this phi belongs to
@@ -154,6 +157,7 @@ pub const LLVMEmitter = struct {
.any_struct_type = null,
.closure_struct_type = null,
.field_name_arrays = std.AutoHashMap(u32, c.LLVMValueRef).init(alloc),
.target_config = target_config,
};
}
@@ -195,6 +199,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,
// so struct sizes will differ. LLVM handles emission correctly.
if (self.target_config.isWasm()) return;
const dl = c.LLVMGetModuleDataLayout(self.llvm_module);
if (dl == null) return;
const type_count = self.ir_mod.types.infos.items.len;
@@ -290,6 +297,19 @@ pub const LLVMEmitter = struct {
fn declareFunction(self: *LLVMEmitter, func: *const Function, func_idx: u32) void {
const name = self.ir_mod.types.getString(func.name);
// Skip builtins that are declared via getOrDeclare* with correct C-compatible types.
// The IR lowering creates extern stubs with IR types (e.g. memset → void return),
// but the C ABI may differ (memset returns ptr). Let getOrDeclare* handle these.
if (func.is_extern and isBuiltinLibcName(name)) {
// Still register in func_map so call resolution works
const builtin_fn = self.getOrDeclareBuiltinByName(name);
if (builtin_fn) |bf| {
self.func_map.put(func_idx, bf) catch unreachable;
return;
}
}
const is_main = std.mem.eql(u8, name, "main");
// main always returns i32 at the LLVM level (JIT expects it)
@@ -663,7 +683,7 @@ pub const LLVMEmitter = struct {
},
.heap_alloc => |un| {
// malloc(size) → *void
const size = self.resolveRef(un.operand);
const size = self.coerceArg(self.resolveRef(un.operand), self.sizeType());
const malloc_fn = self.getOrDeclareMalloc();
var args = [_]c.LLVMValueRef{size};
const result = c.LLVMBuildCall2(
@@ -977,7 +997,7 @@ pub const LLVMEmitter = struct {
if (val_w > elt_w) {
field_val = c.LLVMBuildTrunc(self.builder, field_val, elt, "atrunc");
} else if (val_w < elt_w) {
field_val = c.LLVMBuildSExt(self.builder, field_val, elt, "aext");
field_val = c.LLVMBuildZExt(self.builder, field_val, elt, "aext");
}
}
}
@@ -1277,12 +1297,12 @@ pub const LLVMEmitter = struct {
const base = self.resolveRef(ss.base);
var lo = self.resolveRef(ss.lo);
var hi = self.resolveRef(ss.hi);
// Normalize lo/hi to i64 for consistent arithmetic
// Normalize lo/hi to i64 for consistent arithmetic (indices are unsigned)
if (c.LLVMTypeOf(lo) != self.cached_i64) {
lo = c.LLVMBuildSExt(self.builder, lo, self.cached_i64, "ss.lo64");
lo = c.LLVMBuildZExt(self.builder, lo, self.cached_i64, "ss.lo64");
}
if (c.LLVMTypeOf(hi) != self.cached_i64) {
hi = c.LLVMBuildSExt(self.builder, hi, self.cached_i64, "ss.hi64");
hi = c.LLVMBuildZExt(self.builder, hi, self.cached_i64, "ss.hi64");
}
const base_ty = c.LLVMTypeOf(base);
const base_kind = c.LLVMGetTypeKind(base_ty);
@@ -1354,7 +1374,7 @@ pub const LLVMEmitter = struct {
// Builtins that map to libc functions or LLVM intrinsics
switch (bi.builtin) {
.malloc => {
const size = self.resolveRef(bi.args[0]);
const size = self.coerceArg(self.resolveRef(bi.args[0]), self.sizeType());
const malloc_fn = self.getOrDeclareMalloc();
var args = [_]c.LLVMValueRef{size};
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMallocType(), malloc_fn, &args, 1, "malloc"));
@@ -1369,7 +1389,7 @@ pub const LLVMEmitter = struct {
.memcpy => {
const dst = self.resolveRef(bi.args[0]);
const src = self.resolveRef(bi.args[1]);
const len = self.resolveRef(bi.args[2]);
const len = self.coerceArg(self.resolveRef(bi.args[2]), self.sizeType());
const memcpy_fn = self.getOrDeclareMemcpy();
var args = [_]c.LLVMValueRef{ dst, src, len };
_ = c.LLVMBuildCall2(self.builder, self.getMemcpyType(), memcpy_fn, &args, 3, "");
@@ -1378,11 +1398,9 @@ pub const LLVMEmitter = struct {
.memset => {
const dst = self.resolveRef(bi.args[0]);
var val = self.resolveRef(bi.args[1]);
const len = self.resolveRef(bi.args[2]);
// memset expects i32 for byte value — truncate if needed
if (c.LLVMTypeOf(val) != self.cached_i32) {
val = c.LLVMBuildTrunc(self.builder, val, self.cached_i32, "ms.trunc");
}
const len = self.coerceArg(self.resolveRef(bi.args[2]), self.sizeType());
// memset expects i32 for byte value — coerce width
val = self.coerceArg(val, self.cached_i32);
const memset_fn = self.getOrDeclareMemset();
var args = [_]c.LLVMValueRef{ dst, val, len };
_ = c.LLVMBuildCall2(self.builder, self.getMemsetType(), memset_fn, &args, 3, "");
@@ -1408,11 +1426,16 @@ pub const LLVMEmitter = struct {
const str_val = self.resolveRef(bi.args[0]);
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())
c.LLVMBuildTrunc(self.builder, str_len, self.cached_i32, "len.tr")
else
str_len;
const write_fn = self.getOrDeclareWrite();
var write_args = [_]c.LLVMValueRef{
c.LLVMConstInt(self.cached_i32, 1, 0), // fd = stdout
raw_ptr,
str_len,
count,
};
_ = c.LLVMBuildCall2(self.builder, self.getWriteType(), write_fn, &write_args, 3, "");
self.advanceRefCounter();
@@ -1579,15 +1602,26 @@ pub const LLVMEmitter = struct {
const a_ty = c.LLVMTypeOf(a);
const kind = c.LLVMGetTypeKind(a_ty);
if (kind == c.LLVMStructTypeKind) {
const has = c.LLVMBuildExtractValue(self.builder, a, 1, "oc.has");
const unwrapped = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.val");
// Coerce b_val to match unwrapped type
const uw_ty = c.LLVMTypeOf(unwrapped);
const b_ty = c.LLVMTypeOf(b_val);
if (uw_ty != b_ty) {
b_val = self.coerceArg(b_val, uw_ty);
const n_fields = c.LLVMCountStructElementTypes(a_ty);
const f1_ty = if (n_fields >= 2) c.LLVMStructGetTypeAtIndex(a_ty, 1) else null;
const is_ti1 = if (f1_ty) |ft| c.LLVMGetTypeKind(ft) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(ft) == 1 else false;
if (is_ti1) {
// Standard optional {T, i1}: extract has_value and unwrap
const has = c.LLVMBuildExtractValue(self.builder, a, 1, "oc.has");
const unwrapped = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.val");
const uw_ty = c.LLVMTypeOf(unwrapped);
const b_ty = c.LLVMTypeOf(b_val);
if (uw_ty != b_ty) {
b_val = self.coerceArg(b_val, uw_ty);
}
self.mapRef(c.LLVMBuildSelect(self.builder, has, unwrapped, b_val, "oc.sel"));
} else {
// ?Closure {fn_ptr, env}: check if fn_ptr is null
const fn_ptr = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.fn");
const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oc.nn");
// Select the full closure struct, not just the fn_ptr
self.mapRef(c.LLVMBuildSelect(self.builder, is_nonnull, a, b_val, "oc.sel"));
}
self.mapRef(c.LLVMBuildSelect(self.builder, has, unwrapped, b_val, "oc.sel"));
} else {
// ?*T — select on null
const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, a, c.LLVMConstNull(a_ty), "oc.nn");
@@ -1927,8 +1961,14 @@ pub const LLVMEmitter = struct {
if (kind == c.LLVMIntegerTypeKind and rhs_kind == c.LLVMIntegerTypeKind) {
const lw = c.LLVMGetIntTypeWidth(lhs_ty);
const rw = c.LLVMGetIntTypeWidth(rhs_ty);
if (lw < rw) lhs = c.LLVMBuildZExt(self.builder, lhs, rhs_ty, "cmp.ext")
else if (rw < lw) rhs = c.LLVMBuildZExt(self.builder, rhs, lhs_ty, "cmp.ext");
const is_unsigned = self.isRefUnsigned(bin.lhs) or self.isRefUnsigned(bin.rhs);
if (is_unsigned) {
if (lw < rw) lhs = c.LLVMBuildZExt(self.builder, lhs, rhs_ty, "cmp.ext")
else if (rw < lw) rhs = c.LLVMBuildZExt(self.builder, rhs, lhs_ty, "cmp.ext");
} else {
if (lw < rw) lhs = c.LLVMBuildSExt(self.builder, lhs, rhs_ty, "cmp.ext")
else if (rw < lw) rhs = c.LLVMBuildSExt(self.builder, rhs, lhs_ty, "cmp.ext");
}
}
// Pointer vs integer: coerce int to null pointer
if (kind == c.LLVMPointerTypeKind and rhs_kind == c.LLVMIntegerTypeKind) {
@@ -1981,7 +2021,6 @@ pub const LLVMEmitter = struct {
const lhs = self.resolveRef(bin.lhs);
const rhs = self.resolveRef(bin.rhs);
const b = self.builder;
const i64_ty = c.LLVMInt64TypeInContext(self.context);
const i32_ty = c.LLVMInt32TypeInContext(self.context);
const i1_ty = c.LLVMInt1TypeInContext(self.context);
const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0);
@@ -2005,12 +2044,14 @@ pub const LLVMEmitter = struct {
// memcmp block
c.LLVMPositionBuilderAtEnd(b, memcmp_bb);
const size_ty = self.sizeType();
const memcmp_fn = c.LLVMGetNamedFunction(self.llvm_module, "memcmp") orelse blk: {
var params = [_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty };
var params = [_]c.LLVMTypeRef{ ptr_ty, ptr_ty, size_ty };
const fn_type = c.LLVMFunctionType(i32_ty, &params, 3, 0);
break :blk c.LLVMAddFunction(self.llvm_module, "memcmp", fn_type);
};
var args = [_]c.LLVMValueRef{ lhs_ptr, rhs_ptr, lhs_len };
const cmp_len = self.coerceArg(lhs_len, size_ty);
var args = [_]c.LLVMValueRef{ lhs_ptr, rhs_ptr, cmp_len };
const fn_ty = c.LLVMGlobalGetValueType(memcmp_fn);
const cmp_result = c.LLVMBuildCall2(b, fn_ty, memcmp_fn, &args, 3, "memcmp");
const content_eq = c.LLVMBuildICmp(b, c.LLVMIntEQ, cmp_result, c.LLVMConstInt(i32_ty, 0, 0), "str.ceq");
@@ -2090,9 +2131,14 @@ 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.
fn sizeType(self: *LLVMEmitter) c.LLVMTypeRef {
return if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64;
}
fn getMallocType(self: *LLVMEmitter) c.LLVMTypeRef {
// malloc(i64) → ptr
var param_types = [_]c.LLVMTypeRef{self.cached_i64};
// malloc(size_t) → ptr
var param_types = [_]c.LLVMTypeRef{self.sizeType()};
return c.LLVMFunctionType(self.cached_ptr, &param_types, 1, 0);
}
@@ -2108,8 +2154,8 @@ pub const LLVMEmitter = struct {
}
fn getMemcpyType(self: *LLVMEmitter) c.LLVMTypeRef {
// memcpy(ptr, ptr, i64) → ptr
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.cached_i64 };
// memcpy(ptr, ptr, size_t) → ptr
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.sizeType() };
return c.LLVMFunctionType(self.cached_ptr, &param_types, 3, 0);
}
@@ -2119,8 +2165,8 @@ pub const LLVMEmitter = struct {
}
fn getMemsetType(self: *LLVMEmitter) c.LLVMTypeRef {
// memset(ptr, i32, i64) → ptr
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_i32, self.cached_i64 };
// memset(ptr, i32, size_t) → ptr
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_i32, self.sizeType() };
return c.LLVMFunctionType(self.cached_ptr, &param_types, 3, 0);
}
@@ -2158,15 +2204,24 @@ pub const LLVMEmitter = struct {
return c.LLVMFunctionType(self.cached_f32, &param_types, 1, 0);
}
fn getOrDeclareMemcmp(self: *LLVMEmitter) c.LLVMValueRef {
if (c.LLVMGetNamedFunction(self.llvm_module, "memcmp")) |f| return f;
// memcmp(ptr, ptr, size_t) → i32
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.sizeType() };
const fn_ty = c.LLVMFunctionType(self.cached_i32, &param_types, 3, 0);
return c.LLVMAddFunction(self.llvm_module, "memcmp", fn_ty);
}
fn getOrDeclareWrite(self: *LLVMEmitter) c.LLVMValueRef {
if (c.LLVMGetNamedFunction(self.llvm_module, "write")) |f| return f;
return c.LLVMAddFunction(self.llvm_module, "write", self.getWriteType());
}
fn getWriteType(self: *LLVMEmitter) c.LLVMTypeRef {
// write(fd: i32, buf: ptr, count: i64) → i64
var param_types = [_]c.LLVMTypeRef{ self.cached_i32, self.cached_ptr, self.cached_i64 };
return c.LLVMFunctionType(self.cached_i64, &param_types, 3, 0);
// write(fd: i32, buf: ptr, count: size_t) → ssize_t
const st = self.sizeType();
var param_types = [_]c.LLVMTypeRef{ self.cached_i32, self.cached_ptr, st };
return c.LLVMFunctionType(st, &param_types, 3, 0);
}
fn getOrDeclareSnprintf(self: *LLVMEmitter) c.LLVMValueRef {
@@ -2180,6 +2235,28 @@ pub const LLVMEmitter = struct {
return c.LLVMFunctionType(self.cached_i32, &param_types, 3, 1); // 1 = variadic
}
/// Check if a function name is a known libc builtin that has a dedicated
/// getOrDeclare* helper with correct C-compatible types.
fn isBuiltinLibcName(name: []const u8) bool {
const builtins = [_][]const u8{ "malloc", "free", "memcpy", "memset", "memcmp", "write", "snprintf" };
for (builtins) |b| {
if (std.mem.eql(u8, name, b)) return true;
}
return false;
}
/// Get or declare a builtin libc function by name, using the correct C-compatible type.
fn getOrDeclareBuiltinByName(self: *LLVMEmitter, name: []const u8) ?c.LLVMValueRef {
if (std.mem.eql(u8, name, "malloc")) return self.getOrDeclareMalloc();
if (std.mem.eql(u8, name, "free")) return self.getOrDeclareFree();
if (std.mem.eql(u8, name, "memcpy")) return self.getOrDeclareMemcpy();
if (std.mem.eql(u8, name, "memset")) return self.getOrDeclareMemset();
if (std.mem.eql(u8, name, "memcmp")) return self.getOrDeclareMemcmp();
if (std.mem.eql(u8, name, "write")) return self.getOrDeclareWrite();
if (std.mem.eql(u8, name, "snprintf")) return self.getOrDeclareSnprintf();
return null;
}
/// Build a string fat pointer {ptr, len} from raw pointer and length.
fn buildStringValue(self: *LLVMEmitter, ptr: c.LLVMValueRef, len: c.LLVMValueRef) c.LLVMValueRef {
const str_ty = self.getStringStructType();
@@ -2324,7 +2401,9 @@ pub const LLVMEmitter = struct {
if (val_w > param_w) {
return c.LLVMBuildTrunc(self.builder, val, param_ty, "ca.tr");
} else {
return c.LLVMBuildSExt(self.builder, val, param_ty, "ca.ext");
// Use ZExt by default — preserves bit pattern for unsigned types.
// Signed widening is handled by explicit widen instructions from the IR.
return c.LLVMBuildZExt(self.builder, val, param_ty, "ca.ext");
}
}
// Float → Float (width mismatch)
@@ -2337,8 +2416,14 @@ pub const LLVMEmitter = struct {
return c.LLVMBuildFPTrunc(self.builder, val, param_ty, "ca.fptrunc");
}
}
// Int → Float
// Int → Float (use SIToFP for i1/bool, UIToFP otherwise for safe default)
if (val_kind == c.LLVMIntegerTypeKind and (param_kind == c.LLVMFloatTypeKind or param_kind == c.LLVMDoubleTypeKind)) {
const val_w = c.LLVMGetIntTypeWidth(val_ty);
if (val_w == 1) {
return c.LLVMBuildUIToFP(self.builder, val, param_ty, "ca.uitofp");
}
// Default to SIToFP since most sx integers are signed (s64).
// Explicit unsigned conversions go through the IR widen/narrow path.
return c.LLVMBuildSIToFP(self.builder, val, param_ty, "ca.sitofp");
}
// Float → Int
@@ -2592,13 +2677,22 @@ pub const LLVMEmitter = struct {
// - HFA (homogeneous float aggregate) → leave as-is (LLVM handles it)
fn abiCoerceParamType(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef {
// String/slice → raw pointer
// String/slice → raw pointer (universal across all targets for foreign calls)
if (ir_ty == .string) return self.cached_ptr;
if (!ir_ty.isBuiltin()) {
const info = self.ir_mod.types.get(ir_ty);
if (info == .slice) return self.cached_ptr;
}
// WASM32: i64 → i32 for C ABI (size_t/ssize_t are 32-bit on wasm32)
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;
}
return llvm_ty;
}
// Only coerce struct types
if (c.LLVMGetTypeKind(llvm_ty) != c.LLVMStructTypeKind) return llvm_ty;
@@ -2947,6 +3041,11 @@ pub const LLVMEmitter = struct {
if (ref.isNone()) return false;
const func = &self.ir_mod.functions.items[self.current_func_idx];
const ref_idx = ref.index();
// Check function parameters first (refs 0..N-1)
if (ref_idx < func.params.len) {
const ty = func.params[ref_idx].ty;
return ty == .u8 or ty == .u16 or ty == .u32 or ty == .u64;
}
for (func.blocks.items) |*block| {
const first = block.first_ref;
if (ref_idx >= first and ref_idx < first + @as(u32, @intCast(block.insts.items.len))) {

View File

@@ -1063,7 +1063,12 @@ pub const Lowering = struct {
// Fallback: global variable assignment
if (!handled) {
if (self.global_names.get(id.name)) |gi| {
self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = val } }, .void);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != gi.ty and val_ty != .void and gi.ty != .void)
self.coerceToType(val, val_ty, gi.ty)
else
val;
self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = store_val } }, .void);
}
}
},
@@ -1178,7 +1183,20 @@ pub const Lowering = struct {
.deref_expr => |de| {
const ptr = self.lowerExpr(de.operand);
if (asgn.op == .assign) {
self.builder.store(ptr, val);
const pointee_ty = blk: {
const ptr_ty = self.inferExprType(de.operand);
if (!ptr_ty.isBuiltin()) {
const info = self.module.types.get(ptr_ty);
if (info == .pointer) break :blk info.pointer.pointee;
}
break :blk ptr_ty;
};
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != pointee_ty and val_ty != .void and pointee_ty != .void)
self.coerceToType(val, val_ty, pointee_ty)
else
val;
self.builder.store(ptr, store_val);
} else {
const pointee_ty = self.inferExprType(de.operand);
const elem_ty = blk: {
@@ -1266,7 +1284,12 @@ pub const Lowering = struct {
/// Store a value to a GEP, handling both plain and compound assignment.
fn storeOrCompound(self: *Lowering, gep: Ref, val: Ref, op: ast.Assignment.Op, ty: TypeId) void {
if (op == .assign) {
self.builder.store(gep, val);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != ty and val_ty != .void and ty != .void)
self.coerceToType(val, val_ty, ty)
else
val;
self.builder.store(gep, store_val);
} else {
const loaded = self.builder.load(gep, ty);
const result = self.emitCompoundOp(loaded, val, op, ty);
@@ -1433,7 +1456,7 @@ pub const Lowering = struct {
break :blk switch (uop.op) {
.negate => self.builder.emit(.{ .neg = .{ .operand = operand } }, self.inferExprType(uop.operand)),
.not => self.builder.emit(.{ .bool_not = .{ .operand = operand } }, .bool),
.bit_not => self.builder.emit(.{ .bit_not = .{ .operand = operand } }, .s64),
.bit_not => self.builder.emit(.{ .bit_not = .{ .operand = operand } }, self.inferExprType(uop.operand)),
.xx => self.lowerXX(operand, uop.operand),
.address_of => blk2: {
const inner_ty = self.inferExprType(uop.operand);
@@ -1522,6 +1545,33 @@ pub const Lowering = struct {
}
fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
// Short-circuit: `a and b` → if a then b else false
if (bop.op == .and_op) {
const lhs = self.lowerExpr(bop.lhs);
const rhs_bb = self.freshBlock("and.rhs");
const merge_bb = self.freshBlockWithParams("and.merge", &.{.bool});
const false_val = self.builder.constBool(false);
self.builder.condBr(lhs, rhs_bb, &.{}, merge_bb, &.{false_val});
self.builder.switchToBlock(rhs_bb);
const rhs = self.lowerExpr(bop.rhs);
self.builder.br(merge_bb, &.{rhs});
self.builder.switchToBlock(merge_bb);
return self.builder.blockParam(merge_bb, 0, .bool);
}
// Short-circuit: `a or b` → if a then true else b
if (bop.op == .or_op) {
const lhs = self.lowerExpr(bop.lhs);
const rhs_bb = self.freshBlock("or.rhs");
const merge_bb = self.freshBlockWithParams("or.merge", &.{.bool});
const true_val = self.builder.constBool(true);
self.builder.condBr(lhs, merge_bb, &.{true_val}, rhs_bb, &.{});
self.builder.switchToBlock(rhs_bb);
const rhs = self.lowerExpr(bop.rhs);
self.builder.br(merge_bb, &.{rhs});
self.builder.switchToBlock(merge_bb);
return self.builder.blockParam(merge_bb, 0, .bool);
}
// Special case: optional == null / optional != null
if (bop.op == .eq or bop.op == .neq) {
const lhs_is_null = bop.lhs.data == .null_literal;
@@ -1879,8 +1929,12 @@ pub const Lowering = struct {
const saved_target = self.target_type;
if (is_value and result_type != .void) self.target_type = result_type;
if (is_value) {
const v = self.lowerExpr(ie.then_branch);
var v = self.lowerExpr(ie.then_branch);
if (!self.currentBlockHasTerminator()) {
const v_ty = self.builder.getRefType(v);
if (v_ty != result_type and v_ty != .void and result_type != .void) {
v = self.coerceToType(v, v_ty, result_type);
}
self.builder.br(merge_bb, &.{v});
}
} else {
@@ -1894,8 +1948,12 @@ pub const Lowering = struct {
if (has_else) {
self.builder.switchToBlock(else_bb.?);
if (is_value) {
const v = self.lowerExpr(ie.else_branch.?);
var v = self.lowerExpr(ie.else_branch.?);
if (!self.currentBlockHasTerminator()) {
const v_ty = self.builder.getRefType(v);
if (v_ty != result_type and v_ty != .void and result_type != .void) {
v = self.coerceToType(v, v_ty, result_type);
}
self.builder.br(merge_bb, &.{v});
}
} else {
@@ -3225,9 +3283,37 @@ pub const Lowering = struct {
fn lowerNullCoalesce(self: *Lowering, nc: *const ast.NullCoalesce) Ref {
const lhs = self.lowerExpr(nc.lhs);
const rhs = self.lowerExpr(nc.rhs);
const inner_ty = self.resolveOptionalInner(self.inferExprType(nc.lhs));
return self.builder.emit(.{ .optional_coalesce = .{ .lhs = lhs, .rhs = rhs } }, inner_ty);
// Short-circuit: only evaluate RHS if LHS is null.
// IMPORTANT: optional_unwrap must be in the "has value" branch,
// not before the condBr — the interpreter errors on unwrapping null.
const has_val = self.builder.emit(.{ .optional_has_value = .{ .operand = lhs } }, .bool);
const then_bb = self.freshBlock("nc.has");
const rhs_bb = self.freshBlock("nc.rhs");
const merge_bb = self.freshBlockWithParams("nc.merge", &.{inner_ty});
// If has value, go to then_bb to unwrap; else go to rhs_bb
self.builder.condBr(has_val, then_bb, &.{}, rhs_bb, &.{});
// Then block: unwrap LHS and branch to merge
self.builder.switchToBlock(then_bb);
const unwrapped = self.builder.optionalUnwrap(lhs, inner_ty);
self.builder.br(merge_bb, &.{unwrapped});
// RHS block: evaluate fallback and branch to merge
self.builder.switchToBlock(rhs_bb);
var rhs = self.lowerExpr(nc.rhs);
const rhs_ty = self.builder.getRefType(rhs);
if (rhs_ty != inner_ty and rhs_ty != .void and inner_ty != .void) {
rhs = self.coerceToType(rhs, rhs_ty, inner_ty);
}
self.builder.br(merge_bb, &.{rhs});
// Continue at merge
self.builder.switchToBlock(merge_bb);
return self.builder.blockParam(merge_bb, 0, inner_ty);
}
fn resolveOptionalInner(self: *Lowering, ty: TypeId) TypeId {
@@ -3443,13 +3529,15 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(func_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
// Pack variadic args into a slice if the function has a variadic param
if (self.fn_ast_map.get(func_name)) |fd| {
self.packVariadicCallArgs(fd, c, &args);
}
// Coerce arguments to match parameter types
self.coerceCallArgs(args.items, func.params);
return self.builder.call(fid, args.items, func.ret);
self.coerceCallArgs(args.items, params);
return self.builder.call(fid, args.items, ret_ty);
}
}
// May be a variable holding a function pointer (non-closure)
@@ -3600,11 +3688,13 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(effective_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
if (self.fn_ast_map.get(effective_name)) |fd| {
self.packVariadicCallArgs(fd, c, &args);
}
self.coerceCallArgs(args.items, func.params);
return self.builder.call(fid, args.items, func.ret);
self.coerceCallArgs(args.items, params);
return self.builder.call(fid, args.items, ret_ty);
}
// Check if this is Type.variant(payload) — qualified enum construction
if (ns_name) |type_name| {
@@ -3689,9 +3779,11 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(mangled)) |fid| {
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.coerceCallArgs(method_args.items, func.params);
return self.builder.call(fid, method_args.items, func.ret);
self.coerceCallArgs(method_args.items, params);
return self.builder.call(fid, method_args.items, ret_ty);
}
}
}
@@ -3706,16 +3798,21 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(qualified)) |fid| {
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.coerceCallArgs(method_args.items, func.params);
return self.builder.call(fid, method_args.items, func.ret);
// Note: coerceCallArgs can trigger protocol thunk creation
// (module.addFunction), invalidating func pointer.
// Use pre-extracted params/ret_ty instead of func.* after this.
self.coerceCallArgs(method_args.items, params);
return self.builder.call(fid, method_args.items, ret_ty);
}
}
// Try to resolve as bare function name (method)
if (self.resolveFuncByName(fa.field)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
return self.builder.call(fid, method_args.items, func.ret);
const ret_ty = self.module.functions.items[@intFromEnum(fid)].ret;
return self.builder.call(fid, method_args.items, ret_ty);
}
return self.emitPlaceholder(fa.field);
},
@@ -3739,8 +3836,10 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(qualified)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
self.coerceCallArgs(args.items, func.params);
return self.builder.call(fid, args.items, func.ret);
const ret_ty = func.ret;
const params = func.params;
self.coerceCallArgs(args.items, params);
return self.builder.call(fid, args.items, ret_ty);
}
}
}
@@ -4014,7 +4113,9 @@ pub const Lowering = struct {
if (ret_ty != .void) {
if (self.lowerBlockValue(lam.body)) |val| {
if (!self.currentBlockHasTerminator()) {
self.builder.ret(val, ret_ty);
const val_ty = self.builder.getRefType(val);
const coerced = if (val_ty != .void) self.coerceToType(val, val_ty, ret_ty) else val;
self.builder.ret(coerced, ret_ty);
}
}
} else {
@@ -4243,6 +4344,33 @@ pub const Lowering = struct {
.deref_expr => |de| {
self.collectCaptures(de.operand, param_names, captures);
},
.for_expr => |fe| {
self.collectCaptures(fe.iterable, param_names, captures);
// Register capture name as local so it's not captured
param_names.put(fe.capture_name, {}) catch {};
self.collectCaptures(fe.body, param_names, captures);
},
.slice_expr => |se| {
self.collectCaptures(se.object, param_names, captures);
if (se.start) |s| self.collectCaptures(s, param_names, captures);
if (se.end) |e| self.collectCaptures(e, param_names, captures);
},
.tuple_literal => |tl| {
for (tl.elements) |elem| {
self.collectCaptures(elem.value, param_names, captures);
}
},
.force_unwrap => |fu| {
self.collectCaptures(fu.operand, param_names, captures);
},
.chained_comparison => |cc| {
for (cc.operands) |op| {
self.collectCaptures(op, param_names, captures);
}
},
.defer_stmt => |ds| {
self.collectCaptures(ds.expr, param_names, captures);
},
else => {},
}
}
@@ -4283,23 +4411,22 @@ pub const Lowering = struct {
fn lowerChainedComparison(self: *Lowering, cc: *const ast.ChainedComparison) Ref {
// a < b < c → (a < b) and (b < c)
// Pre-lower all operands so shared ones (e.g., b) aren't evaluated twice.
if (cc.operands.len < 2 or cc.ops.len == 0) {
return self.builder.constBool(true);
}
var result = self.emitCmp(
self.lowerExpr(cc.operands[0]),
self.lowerExpr(cc.operands[1]),
cc.ops[0],
);
var refs = std.ArrayList(Ref).empty;
defer refs.deinit(self.alloc);
for (cc.operands) |op| {
refs.append(self.alloc, self.lowerExpr(op)) catch unreachable;
}
var result = self.emitCmp(refs.items[0], refs.items[1], cc.ops[0]);
var i: usize = 1;
while (i < cc.ops.len) : (i += 1) {
const next_cmp = self.emitCmp(
self.lowerExpr(cc.operands[i]),
self.lowerExpr(cc.operands[i + 1]),
cc.ops[i],
);
const next_cmp = self.emitCmp(refs.items[i], refs.items[i + 1], cc.ops[i]);
result = self.builder.emit(.{ .bool_and = .{ .lhs = result, .rhs = next_cmp } }, .bool);
}
@@ -4380,7 +4507,12 @@ pub const Lowering = struct {
if (self.scope) |scope| {
if (scope.lookup(id.name)) |binding| {
if (binding.is_alloca) {
self.builder.store(binding.ref, val);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != binding.ty and val_ty != .void and binding.ty != .void)
self.coerceToType(val, val_ty, binding.ty)
else
val;
self.builder.store(binding.ref, store_val);
}
}
}
@@ -4390,16 +4522,21 @@ pub const Lowering = struct {
const obj_ty = self.inferExprType(ie.object);
const elem_ty = self.getElementType(obj_ty);
const ptr_ty = self.module.types.ptrTo(elem_ty);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != elem_ty and val_ty != .void and elem_ty != .void)
self.coerceToType(val, val_ty, elem_ty)
else
val;
// For fixed-size arrays, use the alloca pointer directly
const is_array = !obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .array;
const obj_alloca = if (is_array) self.getExprAlloca(ie.object) else null;
if (obj_alloca) |alloca_ref| {
const gep = self.builder.emit(.{ .index_gep = .{ .lhs = alloca_ref, .rhs = idx } }, ptr_ty);
self.builder.store(gep, val);
self.builder.store(gep, store_val);
} else {
const obj = self.lowerExpr(ie.object);
const gep = self.builder.emit(.{ .index_gep = .{ .lhs = obj, .rhs = idx } }, ptr_ty);
self.builder.store(gep, val);
self.builder.store(gep, store_val);
}
},
.field_access => |fa| {
@@ -4417,11 +4554,29 @@ pub const Lowering = struct {
}
}
const gep = self.builder.structGepTyped(obj_ptr, field_idx, field_ty, obj_ty);
self.builder.store(gep, val);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != field_ty and val_ty != .void and field_ty != .void)
self.coerceToType(val, val_ty, field_ty)
else
val;
self.builder.store(gep, store_val);
},
.deref_expr => |de| {
const ptr = self.lowerExpr(de.operand);
self.builder.store(ptr, val);
const pointee_ty = blk: {
const ptr_ty = self.inferExprType(de.operand);
if (!ptr_ty.isBuiltin()) {
const info = self.module.types.get(ptr_ty);
if (info == .pointer) break :blk info.pointer.pointee;
}
break :blk ptr_ty;
};
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != pointee_ty and val_ty != .void and pointee_ty != .void)
self.coerceToType(val, val_ty, pointee_ty)
else
val;
self.builder.store(ptr, store_val);
},
else => {
_ = self.emitPlaceholder("multi_assign_target");
@@ -4995,6 +5150,8 @@ pub const Lowering = struct {
// Resolve the monomorphized function and call it (stripping type args)
if (self.resolveFuncByName(mangled_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
// Build value-only args (skip type param declaration args)
// Use separate index for lowered_args since type params don't consume call args
var value_args = std.ArrayList(Ref).empty;
@@ -5011,8 +5168,8 @@ pub const Lowering = struct {
}
arg_idx += 1;
}
self.coerceCallArgs(value_args.items, func.params);
return self.builder.call(fid, value_args.items, func.ret);
self.coerceCallArgs(value_args.items, params);
return self.builder.call(fid, value_args.items, ret_ty);
}
return self.emitPlaceholder(base_name);
@@ -5209,6 +5366,8 @@ pub const Lowering = struct {
// Build call args (replace cast arg with unboxed value, skip type param decl args)
if (self.resolveFuncByName(mangled_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const callee_ret = func.ret;
const callee_params = func.params;
var call_args = std.ArrayList(Ref).empty;
defer call_args.deinit(self.alloc);
for (fd.params, 0..) |p, pi| {
@@ -5221,8 +5380,8 @@ pub const Lowering = struct {
}
}
}
self.coerceCallArgs(call_args.items, func.params);
const result = self.builder.call(fid, call_args.items, func.ret);
self.coerceCallArgs(call_args.items, callee_params);
const result = self.builder.call(fid, call_args.items, callee_ret);
if (result_slot) |slot| {
self.builder.store(slot, result);
}
@@ -5234,15 +5393,16 @@ pub const Lowering = struct {
self.lazyLowerFunction(resolve_name);
}
if (self.resolveFuncByName(resolve_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const callee_ret = self.module.functions.items[@intFromEnum(fid)].ret;
const callee_params = self.module.functions.items[@intFromEnum(fid)].params;
var call_args = std.ArrayList(Ref).empty;
defer call_args.deinit(self.alloc);
for (fd.params, 0..) |_, pi| {
if (pi == cast_arg_idx) {
// Coerce unboxed value (typed as ty_id) to param type
var arg = unboxed;
if (pi < func.params.len) {
arg = self.coerceToType(arg, ty_id, func.params[pi].ty);
if (pi < callee_params.len) {
arg = self.coerceToType(arg, ty_id, callee_params[pi].ty);
}
call_args.append(self.alloc, arg) catch unreachable;
} else if (pi < other_args.items.len) {
@@ -5252,12 +5412,12 @@ pub const Lowering = struct {
}
}
// Coerce non-cast args (source type unknown, use s64 default)
for (0..@min(call_args.items.len, func.params.len)) |ci| {
for (0..@min(call_args.items.len, callee_params.len)) |ci| {
if (ci != cast_arg_idx) {
call_args.items[ci] = self.coerceToType(call_args.items[ci], .s64, func.params[ci].ty);
call_args.items[ci] = self.coerceToType(call_args.items[ci], .s64, callee_params[ci].ty);
}
}
const result = self.builder.call(fid, call_args.items, func.ret);
const result = self.builder.call(fid, call_args.items, callee_ret);
if (result_slot) |slot| {
self.builder.store(slot, result);
}
@@ -5363,7 +5523,8 @@ pub const Lowering = struct {
const body_val = self.lowerBlockValue(fd.body);
if (!self.currentBlockHasTerminator()) {
if (body_val) |val| {
const coerced = self.coerceToType(val, .s64, ret_ty);
const val_ty = self.builder.getRefType(val);
const coerced = if (val_ty != .void) self.coerceToType(val, val_ty, ret_ty) else val;
self.builder.ret(coerced, ret_ty);
} else {
self.ensureTerminator(ret_ty);
@@ -7372,26 +7533,25 @@ pub const Lowering = struct {
fn inferGenericReturnType(self: *Lowering, fd: *const ast.FnDecl, c: *const ast.Call) TypeId {
if (fd.return_type == null) return .void;
// Build type bindings from call args (same logic as lowerGenericCall)
// Build ALL type bindings from call args before resolving return type
var tmp_bindings = std.StringHashMap(TypeId).init(self.alloc);
defer tmp_bindings.deinit();
for (fd.type_params) |tp| {
// Strategy 1: direct type param decl ($T: Type) — param.name == tp.name
var found = false;
for (fd.params, 0..) |param, pi| {
if (std.mem.eql(u8, param.name, tp.name)) {
if (pi < c.args.len) {
const ty = self.resolveTypeArg(c.args[pi]);
// Resolve return type with this binding
const saved = self.type_bindings;
var tmp_bindings = std.StringHashMap(TypeId).init(self.alloc);
defer tmp_bindings.deinit();
tmp_bindings.put(tp.name, ty) catch {};
self.type_bindings = tmp_bindings;
const ret = self.resolveTypeWithBindings(fd.return_type.?);
self.type_bindings = saved;
return ret;
}
found = true;
break;
}
}
if (found) continue;
// Strategy 2: inferred from usage (a: $T, b: T) — check ALL matching params, pick widest
var inferred_ty: ?TypeId = null;
for (fd.params, 0..) |param, pi| {
@@ -7414,16 +7574,18 @@ pub const Lowering = struct {
}
}
if (inferred_ty) |ty| {
const saved = self.type_bindings;
var tmp_bindings = std.StringHashMap(TypeId).init(self.alloc);
defer tmp_bindings.deinit();
tmp_bindings.put(tp.name, ty) catch {};
self.type_bindings = tmp_bindings;
const ret = self.resolveTypeWithBindings(fd.return_type.?);
self.type_bindings = saved;
return ret;
}
}
// Resolve return type with all bindings
if (tmp_bindings.count() > 0) {
const saved = self.type_bindings;
self.type_bindings = tmp_bindings;
const ret = self.resolveTypeWithBindings(fd.return_type.?);
self.type_bindings = saved;
return ret;
}
return .s64;
}

View File

@@ -195,6 +195,10 @@ pub const Builder = struct {
if (self.func == null) return .s64;
const func = self.currentFunc();
const ref_idx = @intFromEnum(ref);
// Check function parameters first (refs 0..N-1)
if (ref_idx < func.params.len) {
return func.params[ref_idx].ty;
}
for (func.blocks.items) |*block| {
const first = block.first_ref;
if (ref_idx >= first and ref_idx < first + @as(u32, @intCast(block.insts.items.len))) {

View File

@@ -23,6 +23,7 @@ pub fn main(init: std.process.Init) !void {
var input_path: ?[]const u8 = null;
var target_config = sx.target.TargetConfig{};
var lib_paths = std.ArrayList([]const u8).empty;
var link_flags = std.ArrayList([]const u8).empty;
var show_timing: bool = false;
var explicit_opt: bool = false;
var enable_cache: bool = false;
@@ -70,6 +71,10 @@ pub fn main(init: std.process.Init) !void {
if (i >= args.len) { std.debug.print("error: -L requires a value\n", .{}); return; }
try lib_paths.append(allocator, args[i]);
}
} else if (std.mem.eql(u8, arg, "--lflags")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --lflags requires a value\n", .{}); return; }
try link_flags.append(allocator, args[i]);
} else if (!std.mem.startsWith(u8, arg, "-")) {
input_path = arg;
} else {
@@ -79,6 +84,7 @@ pub fn main(init: std.process.Init) !void {
}
target_config.lib_paths = try lib_paths.toOwnedSlice(allocator);
target_config.extra_link_flags = try link_flags.toOwnedSlice(allocator);
const path = input_path orelse {
printUsage();
@@ -86,7 +92,13 @@ pub fn main(init: std.process.Init) !void {
};
if (std.mem.eql(u8, command, "build")) {
const output_name = target_config.output_path orelse deriveOutputName(path);
const output_name = target_config.output_path orelse blk: {
const base = deriveOutputName(path);
if (target_config.isEmscripten()) {
break :blk try std.fmt.allocPrint(allocator, "{s}.html", .{base});
}
break :blk base;
};
compile(allocator, io, path, output_name, target_config, show_timing, enable_cache) catch return;
std.debug.print("compiled: {s}\n", .{output_name});
} else if (std.mem.eql(u8, command, "ir")) {
@@ -96,6 +108,10 @@ pub fn main(init: std.process.Init) !void {
} else if (std.mem.eql(u8, command, "asm")) {
emitAsm(allocator, io, path, target_config) catch return;
} else if (std.mem.eql(u8, command, "run")) {
if (target_config.isWasm()) {
std.debug.print("error: 'run' is not supported for wasm targets. Use 'build' instead.\n", .{});
return;
}
// Default to -O0 for run (faster compile) unless user explicitly set --opt
if (!explicit_opt) target_config.opt_level = .none;
var timer = Timing.init(show_timing);
@@ -238,6 +254,7 @@ fn printUsage() void {
\\ -L <path> Library search path (repeatable)
\\ --linker <cmd> Linker command (default: cc)
\\ --sysroot <path> Sysroot for cross-compilation
\\ --lflags <flag> Extra linker flag (repeatable, e.g. --lflags -sUSE_SDL=2)
\\ --cache Enable build caching
\\ --time Show compilation timing breakdown
\\

View File

@@ -19,6 +19,8 @@ pub const TargetConfig = struct {
linker: ?[]const u8 = null,
/// Sysroot for cross-compilation (passed as --sysroot to linker).
sysroot: ?[]const u8 = null,
/// Extra flags passed through to the linker (e.g. Emscripten -s flags).
extra_link_flags: []const []const u8 = &.{},
pub const OptLevel = enum {
none,
@@ -51,6 +53,16 @@ pub const TargetConfig = struct {
return self.tripleContains("windows") or self.tripleContains("win32");
}
/// Check if target triple indicates WebAssembly (wasm32 or wasm64).
pub fn isWasm(self: TargetConfig) bool {
return self.tripleHasPrefix("wasm32", "wasm64");
}
/// Check if target triple indicates Emscripten (contains "emscripten").
pub fn isEmscripten(self: TargetConfig) bool {
return self.tripleContains("emscripten");
}
fn tripleHasPrefix(self: TargetConfig, prefix1: []const u8, prefix2: []const u8) bool {
if (self.triple) |t| {
const span = std.mem.span(t);
@@ -139,7 +151,28 @@ pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 {
pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, extra_objects: []const []const u8, output_bin: []const u8, libraries: []const []const u8, target_config: TargetConfig) !void {
var argv = std.ArrayList([]const u8).empty;
if (target_config.isWindows()) {
if (target_config.isEmscripten()) {
// Emscripten: use emcc as the linker/driver
const linker = target_config.linker orelse "emcc";
try argv.appendSlice(allocator, &.{ linker, output_obj, "-o", output_bin });
for (extra_objects) |eo| try argv.append(allocator, eo);
if (target_config.sysroot) |sr| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "--sysroot={s}", .{sr}));
}
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}));
}
// Extra linker flags (e.g. -sUSE_SDL=2, -sUSE_WEBGL2=1, --preload-file)
for (target_config.extra_link_flags) |flag| {
try argv.append(allocator, flag);
}
} else if (target_config.isWindows()) {
// Windows: MSVC-style linker flags
const linker = target_config.linker orelse "link.exe";
try argv.appendSlice(allocator, &.{ linker, output_obj });
@@ -176,6 +209,11 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
for (libraries) |lib| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib}));
}
// Extra linker flags
for (target_config.extra_link_flags) |flag| {
try argv.append(allocator, flag);
}
}
const argv_slice = try argv.toOwnedSlice(allocator);