wasm
This commit is contained in:
@@ -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, ¶ms, 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, ¶m_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, ¶m_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, ¶m_types, 3, 0);
|
||||
}
|
||||
|
||||
@@ -2158,15 +2204,24 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMFunctionType(self.cached_f32, ¶m_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, ¶m_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, ¶m_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, ¶m_types, 3, 0);
|
||||
}
|
||||
|
||||
fn getOrDeclareSnprintf(self: *LLVMEmitter) c.LLVMValueRef {
|
||||
@@ -2180,6 +2235,28 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMFunctionType(self.cached_i32, ¶m_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))) {
|
||||
|
||||
288
src/ir/lower.zig
288
src/ir/lower.zig
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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))) {
|
||||
|
||||
Reference in New Issue
Block a user