asm...
This commit is contained in:
184
src/ir/lower.zig
184
src/ir/lower.zig
@@ -104,6 +104,13 @@ pub const Lowering = struct {
|
||||
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
|
||||
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // type alias name → target TypeId
|
||||
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name
|
||||
target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if)
|
||||
comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH)
|
||||
|
||||
pub const ComptimeValue = union(enum) {
|
||||
int_val: i64,
|
||||
enum_tag: struct { ty: TypeId, tag: u32 },
|
||||
};
|
||||
|
||||
const StructConstInfo = struct {
|
||||
value: *const Node,
|
||||
@@ -165,12 +172,68 @@ pub const Lowering = struct {
|
||||
};
|
||||
// Pass 1: scan — register all function ASTs, struct types, extern stubs
|
||||
self.scanDecls(decls);
|
||||
// Pass 1b: inject compile-time constants (OS, ARCH, POINTER_SIZE) from target config
|
||||
self.injectComptimeConstants();
|
||||
// Pass 2: lower main (and comptime side-effects)
|
||||
self.lowerMainAndComptime(decls);
|
||||
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
|
||||
self.lowerDeferredTypeFns();
|
||||
}
|
||||
|
||||
/// Inject compile-time constants from target_config into comptime_constants.
|
||||
/// Called after scanDecls so that enum types (OperatingSystem, Architecture) are registered.
|
||||
fn injectComptimeConstants(self: *Lowering) void {
|
||||
const tc = self.target_config orelse return;
|
||||
|
||||
// OS: OperatingSystem enum { macos; linux; windows; wasm; unknown; }
|
||||
const os_name_id = self.module.types.internString("OperatingSystem");
|
||||
if (self.module.types.findByName(os_name_id)) |os_ty| {
|
||||
const os_info = self.module.types.get(os_ty);
|
||||
if (os_info == .@"enum") {
|
||||
const tag: u32 = if (tc.isWasm())
|
||||
self.findVariantIndex(os_info.@"enum".variants, "wasm")
|
||||
else if (tc.isWindows())
|
||||
self.findVariantIndex(os_info.@"enum".variants, "windows")
|
||||
else if (tc.isLinux())
|
||||
self.findVariantIndex(os_info.@"enum".variants, "linux")
|
||||
else if (tc.isMacOS())
|
||||
self.findVariantIndex(os_info.@"enum".variants, "macos")
|
||||
else
|
||||
self.findVariantIndex(os_info.@"enum".variants, "unknown");
|
||||
self.comptime_constants.put("OS", .{ .enum_tag = .{ .ty = os_ty, .tag = tag } }) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// ARCH: Architecture enum { aarch64; x86_64; wasm32; unknown; }
|
||||
const arch_name_id = self.module.types.internString("Architecture");
|
||||
if (self.module.types.findByName(arch_name_id)) |arch_ty| {
|
||||
const arch_info = self.module.types.get(arch_ty);
|
||||
if (arch_info == .@"enum") {
|
||||
const tag: u32 = if (tc.isWasm())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "wasm32")
|
||||
else if (tc.isAarch64())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "aarch64")
|
||||
else if (tc.isX86_64())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "x86_64")
|
||||
else
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "unknown");
|
||||
self.comptime_constants.put("ARCH", .{ .enum_tag = .{ .ty = arch_ty, .tag = tag } }) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// POINTER_SIZE: s64 (4 for wasm, 8 otherwise)
|
||||
const ptr_size: i64 = if (tc.isWasm()) 4 else 8;
|
||||
self.comptime_constants.put("POINTER_SIZE", .{ .int_val = ptr_size }) catch {};
|
||||
}
|
||||
|
||||
fn findVariantIndex(self: *Lowering, variants: []const types.StringId, name: []const u8) u32 {
|
||||
const name_id = self.module.types.internString(name);
|
||||
for (variants, 0..) |v, i| {
|
||||
if (v == name_id) return @intCast(i);
|
||||
}
|
||||
return 0; // fallback to first variant
|
||||
}
|
||||
|
||||
/// Lower functions that were deferred because they use type-category matching.
|
||||
/// At this point, main is fully lowered and all types are in the TypeTable.
|
||||
fn lowerDeferredTypeFns(self: *Lowering) void {
|
||||
@@ -381,6 +444,8 @@ pub const Lowering = struct {
|
||||
const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) {
|
||||
.undef_literal => .zeroinit,
|
||||
.int_literal => |il| .{ .int = il.value },
|
||||
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||
.float_literal => |fl| .{ .float = fl.value },
|
||||
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||
else => null,
|
||||
} else null;
|
||||
@@ -723,6 +788,15 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower an `inline if` branch — block body emits statements, expression returns value.
|
||||
fn lowerInlineBranch(self: *Lowering, node: *const Node) Ref {
|
||||
if (node.data == .block) {
|
||||
self.lowerBlock(node);
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
return self.lowerExpr(node);
|
||||
}
|
||||
|
||||
/// Lower a block and return the last expression's value (for implicit returns).
|
||||
fn lowerBlockValue(self: *Lowering, node: *const Node) ?Ref {
|
||||
// Set force_block_value so nested if-else expressions produce values
|
||||
@@ -1845,6 +1919,19 @@ pub const Lowering = struct {
|
||||
// ── Control flow ────────────────────────────────────────────────
|
||||
|
||||
fn lowerIfExpr(self: *Lowering, ie: *const ast.IfExpr) Ref {
|
||||
// inline if: evaluate condition at compile time, only lower taken branch
|
||||
if (ie.is_comptime) {
|
||||
if (self.evalComptimeCondition(ie.condition)) |is_true| {
|
||||
if (is_true) {
|
||||
return self.lowerInlineBranch(ie.then_branch);
|
||||
} else if (ie.else_branch) |eb| {
|
||||
return self.lowerInlineBranch(eb);
|
||||
}
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
// Condition couldn't be evaluated — fall through to runtime
|
||||
}
|
||||
|
||||
// Check for constant-bool conditions (e.g., is_flags(T) → false) to avoid dead-code LLVM errors
|
||||
if (self.tryConstBoolCondition(ie.condition)) |is_true| {
|
||||
if (is_true) {
|
||||
@@ -1999,6 +2086,46 @@ pub const Lowering = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Evaluate a compile-time condition for `inline if`.
|
||||
/// Handles: `ident == .variant`, `ident != .variant`, `ident == int`, `ident != int`.
|
||||
fn evalComptimeCondition(self: *Lowering, node: *const Node) ?bool {
|
||||
if (node.data != .binary_op) return null;
|
||||
const bo = &node.data.binary_op;
|
||||
if (bo.op != .eq and bo.op != .neq) return null;
|
||||
|
||||
// LHS must be an identifier that's in comptime_constants
|
||||
const name = switch (bo.lhs.data) {
|
||||
.identifier => |id| id.name,
|
||||
else => return null,
|
||||
};
|
||||
const cv = self.comptime_constants.get(name) orelse return null;
|
||||
|
||||
switch (cv) {
|
||||
.enum_tag => |et| {
|
||||
// RHS must be an enum literal (.variant)
|
||||
const variant_name = switch (bo.rhs.data) {
|
||||
.enum_literal => |el| el.name,
|
||||
else => return null,
|
||||
};
|
||||
// Look up variant index in the enum type
|
||||
const enum_info = self.module.types.get(et.ty);
|
||||
if (enum_info != .@"enum") return null;
|
||||
const variant_idx = self.findVariantIndex(enum_info.@"enum".variants, variant_name);
|
||||
const result = et.tag == variant_idx;
|
||||
return if (bo.op == .eq) result else !result;
|
||||
},
|
||||
.int_val => |iv| {
|
||||
// RHS must be an integer literal
|
||||
const rhs_val: i64 = switch (bo.rhs.data) {
|
||||
.int_literal => |il| il.value,
|
||||
else => return null,
|
||||
};
|
||||
const result = iv == rhs_val;
|
||||
return if (bo.op == .eq) result else !result;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref {
|
||||
const header_bb = self.freshBlock("while.hdr");
|
||||
const body_bb = self.freshBlock("while.body");
|
||||
@@ -2857,6 +2984,10 @@ pub const Lowering = struct {
|
||||
const info = self.module.types.get(obj_ty);
|
||||
switch (info) {
|
||||
.tagged_union => |u| {
|
||||
// .tag → extract the enum tag value with the correct tag type
|
||||
if (std.mem.eql(u8, field, "tag")) {
|
||||
return self.builder.emit(.{ .enum_tag = .{ .operand = obj } }, u.tag_type);
|
||||
}
|
||||
// Tagged union — use enum_payload
|
||||
for (u.fields, 0..) |f, i| {
|
||||
if (f.name == field_name_id) {
|
||||
@@ -3727,8 +3858,19 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
// Method call: obj.method(args) → prepend obj (or &obj for *Self receivers)
|
||||
const obj_ty = self.inferExprType(fa.object);
|
||||
const obj = self.lowerExpr(fa.object);
|
||||
// For ptr.*.method(): pass the pointer directly instead of loading + re-addressing.
|
||||
// This ensures mutations through self: *T are visible after the call.
|
||||
var obj_ty: TypeId = undefined;
|
||||
var obj: Ref = undefined;
|
||||
var effective_obj_node: *const Node = fa.object;
|
||||
if (fa.object.data == .deref_expr) {
|
||||
effective_obj_node = fa.object.data.deref_expr.operand;
|
||||
obj_ty = self.inferExprType(effective_obj_node);
|
||||
obj = self.lowerExpr(effective_obj_node);
|
||||
} else {
|
||||
obj_ty = self.inferExprType(fa.object);
|
||||
obj = self.lowerExpr(fa.object);
|
||||
}
|
||||
|
||||
// Check if field is a closure type — call as closure, not method
|
||||
if (!obj_ty.isBuiltin()) {
|
||||
@@ -3781,7 +3923,7 @@ pub const Lowering = struct {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty);
|
||||
self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
|
||||
self.coerceCallArgs(method_args.items, params);
|
||||
return self.builder.call(fid, method_args.items, ret_ty);
|
||||
}
|
||||
@@ -3800,7 +3942,7 @@ pub const Lowering = struct {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty);
|
||||
self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
|
||||
// Note: coerceCallArgs can trigger protocol thunk creation
|
||||
// (module.addFunction), invalidating func pointer.
|
||||
// Use pre-extracted params/ret_ty instead of func.* after this.
|
||||
@@ -4005,6 +4147,14 @@ pub const Lowering = struct {
|
||||
if (!tt.isBuiltin()) {
|
||||
const tti = self.module.types.get(tt);
|
||||
if (tti == .closure) break :blk tti.closure.params;
|
||||
// Unwrap ?Closure(...) → Closure(...)
|
||||
if (tti == .optional) {
|
||||
const inner = tti.optional.child;
|
||||
if (!inner.isBuiltin()) {
|
||||
const inner_info = self.module.types.get(inner);
|
||||
if (inner_info == .closure) break :blk inner_info.closure.params;
|
||||
}
|
||||
}
|
||||
}
|
||||
break :blk null;
|
||||
} else null;
|
||||
@@ -4031,6 +4181,14 @@ pub const Lowering = struct {
|
||||
if (!tt.isBuiltin()) {
|
||||
const tti = self.module.types.get(tt);
|
||||
if (tti == .closure) break :blk tti.closure.ret;
|
||||
// Unwrap ?Closure(...) → Closure(...)
|
||||
if (tti == .optional) {
|
||||
const inner = tti.optional.child;
|
||||
if (!inner.isBuiltin()) {
|
||||
const inner_info = self.module.types.get(inner);
|
||||
if (inner_info == .closure) break :blk inner_info.closure.ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Arrow lambda without explicit return type — infer from body expression
|
||||
@@ -4388,8 +4546,7 @@ pub const Lowering = struct {
|
||||
offset = (offset + field_align - 1) & ~(field_align - 1);
|
||||
offset += field_size;
|
||||
}
|
||||
// Align total to struct alignment (max field alignment, minimum 8)
|
||||
if (max_align < 8) max_align = 8;
|
||||
// Align total to max field alignment (matches LLVM's struct alignment)
|
||||
return (offset + max_align - 1) & ~(max_align - 1);
|
||||
}
|
||||
|
||||
@@ -5716,6 +5873,8 @@ pub const Lowering = struct {
|
||||
if (ty == .void) return "void";
|
||||
if (ty == .string) return "string";
|
||||
if (ty == .any) return "Any";
|
||||
if (ty == .usize) return "usize";
|
||||
if (ty == .isize) return "isize";
|
||||
|
||||
const info = self.module.types.get(ty);
|
||||
return switch (info) {
|
||||
@@ -5880,6 +6039,8 @@ pub const Lowering = struct {
|
||||
if (ty == .void) return "void";
|
||||
if (ty == .string) return "string";
|
||||
if (ty == .any) return "Any";
|
||||
if (ty == .usize) return "usize";
|
||||
if (ty == .isize) return "isize";
|
||||
|
||||
const info = self.module.types.get(ty);
|
||||
return switch (info) {
|
||||
@@ -5932,6 +6093,8 @@ pub const Lowering = struct {
|
||||
tags.append(self.alloc, TypeId.u16.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.u32.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.u64.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.usize.index()) catch {};
|
||||
tags.append(self.alloc, TypeId.isize.index()) catch {};
|
||||
return tags.items;
|
||||
}
|
||||
if (std.mem.eql(u8, name, "float")) {
|
||||
@@ -7220,6 +7383,7 @@ pub const Lowering = struct {
|
||||
// Build call args: ctx + user args
|
||||
// Protocol method params use *void for Self-typed params. If the caller passes
|
||||
// a struct value, we need to alloca+store and pass the pointer instead.
|
||||
// Also coerce argument types to match declared param types (e.g., s64 → s32).
|
||||
var call_args = std.ArrayList(Ref).empty;
|
||||
defer call_args.deinit(self.alloc);
|
||||
call_args.append(self.alloc, ctx) catch unreachable;
|
||||
@@ -7233,7 +7397,9 @@ pub const Lowering = struct {
|
||||
self.builder.store(slot, a);
|
||||
call_args.append(self.alloc, slot) catch unreachable;
|
||||
} else {
|
||||
call_args.append(self.alloc, a) catch unreachable;
|
||||
// Coerce to match declared parameter type (critical for WASM strict signatures)
|
||||
const coerced = self.coerceToType(a, arg_ty, expected_ty);
|
||||
call_args.append(self.alloc, coerced) catch unreachable;
|
||||
}
|
||||
}
|
||||
const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
||||
@@ -8010,7 +8176,7 @@ pub const Lowering = struct {
|
||||
|
||||
fn isInt(ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64 => true,
|
||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
@@ -8034,6 +8200,7 @@ pub const Lowering = struct {
|
||||
.s16, .u16 => 16,
|
||||
.s32, .u32 => 32,
|
||||
.s64, .u64 => 64,
|
||||
.usize, .isize => 0, // target-dependent — use typeBitsEx
|
||||
.f32 => 32,
|
||||
.f64 => 64,
|
||||
else => 0,
|
||||
@@ -8041,6 +8208,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn typeBitsEx(self: *Lowering, ty: TypeId) u32 {
|
||||
if (ty == .usize or ty == .isize) return @as(u32, self.module.types.pointer_size) * 8;
|
||||
const b = typeBits(ty);
|
||||
if (b > 0) return b;
|
||||
if (!ty.isBuiltin()) {
|
||||
|
||||
Reference in New Issue
Block a user