fix(ir): route every comptime-int through the shared evaluator (0083)
Attempts 1–4 fixed the array-dimension paths but the same length-0 fabrication class survived on every other site that resolves a compile-time integer. Unify them all on the single shared `program_index.evalConstIntExpr` so they cannot diverge: - All three Vector lane resolvers (resolveTypeCallWithBindings, resolveParameterizedWithBindings, resolveArrayLiteralType) and both generic value-param binders (instantiateGenericStruct, instantiateTypeFunction) hand-rolled an `else => 0` switch. A module-const lane `Vector(N, f32)` fabricated a 0-lane `<0 x float>` (LLVM "huge alignment" abort); a value-param `Vec(N, f32)` fabricated a 0 binding / wrong mangled name. They now fold through the shared evaluator and emit a clean diagnostic + `.unresolved` on a non-const operand (resolveVectorLane / resolveValueParamArg) — never 0. - evalComptimeInt (inline-for bounds) delegated to the shared evaluator, so `inline for 0..M` / `0..(M+1)` fold like array dims. The `<pack>.len` leaf moved into the shared folder via a new `ctx.lookupPackLen`. - The unknown-type semantic checker no longer walks a value-param position (`Vector(N, …)` / `Vec(N, …)`) as a type name (was reporting "unknown type 'N'"). - The parameterized-type-arg parser and the function-body lookahead (hasFnBodyAfterArrow) accept a const-EXPRESSION in a value position, so `Vector(M + 1, f32)` and `[M + 1]T` parse as a return type too (the latter a pre-existing array-dim sibling that the same heuristic broke). Regressions: examples/1501 (named-const + const-expr lane, direct + alias, 3/4-lane reads), 1502 (runtime lane clean-halts, exit 1, no LLVM crash), 0207 (Vec(N)/Vec(M+1) == Vec(3) instantiation), 0610 (inline-for const bounds). Shared-evaluator unit test extended with the pack-len arm. zig build && zig build test && bash tests/run_examples.sh: 395 passed, 0 failed.
This commit is contained in:
124
src/ir/lower.zig
124
src/ir/lower.zig
@@ -3984,32 +3984,14 @@ pub const Lowering = struct {
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
|
||||
/// Evaluate a node to a comptime integer: literal, comptime-constant
|
||||
/// identifier, or `<pack>.len` (resolves to the monomorphised arity).
|
||||
/// Evaluate an `inline for` range bound to a comptime integer. Delegates to
|
||||
/// the shared `program_index.evalConstIntExpr` — the SAME folder the array
|
||||
/// dimension / Vector lane / value-param paths use — so a literal, a comptime
|
||||
/// constant (cursor), a module/generic const (`inline for 0..M`), a
|
||||
/// `<pack>.len` leaf, and any constant-foldable expression over those
|
||||
/// (`inline for 0..(M + 1)`) all resolve identically. One folder, one answer.
|
||||
fn evalComptimeInt(self: *Lowering, node: *const Node) ?i64 {
|
||||
switch (node.data) {
|
||||
.int_literal => |lit| return lit.value,
|
||||
.identifier => |id| {
|
||||
if (self.comptime_constants.get(id.name)) |cv| {
|
||||
switch (cv) {
|
||||
.int_val => |iv| return iv,
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
.field_access => |fa| {
|
||||
if (self.pack_param_count) |ppc| {
|
||||
if (fa.object.data == .identifier and std.mem.eql(u8, fa.field, "len")) {
|
||||
if (ppc.get(fa.object.data.identifier.name)) |n| {
|
||||
return @as(i64, @intCast(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
return program_index_mod.evalConstIntExpr(node, self);
|
||||
}
|
||||
|
||||
fn lowerMatch(self: *Lowering, me: *const ast.MatchExpr) Ref {
|
||||
@@ -5428,12 +5410,9 @@ pub const Lowering = struct {
|
||||
};
|
||||
if (std.mem.eql(u8, callee_name, "Vector")) {
|
||||
if (cl.args.len == 2) {
|
||||
const length: u32 = switch (cl.args[0].data) {
|
||||
.int_literal => |lit| @intCast(@as(u64, @bitCast(lit.value))),
|
||||
else => 0,
|
||||
};
|
||||
const length = self.resolveVectorLane(cl.args[0]) orelse return .unresolved;
|
||||
const elem = self.resolveTypeWithBindings(cl.args[1]);
|
||||
if (length > 0) return self.module.types.vectorOf(elem, length);
|
||||
return self.module.types.vectorOf(elem, length);
|
||||
}
|
||||
}
|
||||
// Try as generic struct
|
||||
@@ -11699,6 +11678,18 @@ pub const Lowering = struct {
|
||||
return self.comptimeIntNamed(name);
|
||||
}
|
||||
|
||||
/// Pack-length leaf for the shared integer-expression evaluator: a pack
|
||||
/// name's monomorphised arity (e.g. an `inline for 0..xs.len` bound).
|
||||
/// Resolves through `pack_param_count`, which is populated when a comptime
|
||||
/// call binds a pack name. A name with no active pack binding is not a
|
||||
/// compile-time integer leaf here → null.
|
||||
pub fn lookupPackLen(self: *Lowering, name: []const u8) ?i64 {
|
||||
if (self.pack_param_count) |ppc| {
|
||||
if (ppc.get(name)) |n| return @intCast(n);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolve a name to a compile-time integer across the three const tables.
|
||||
fn comptimeIntNamed(self: *Lowering, name: []const u8) ?i64 {
|
||||
if (self.comptime_constants.get(name)) |cv| switch (cv) {
|
||||
@@ -11828,6 +11819,36 @@ pub const Lowering = struct {
|
||||
return .{ .l = self };
|
||||
}
|
||||
|
||||
/// Resolve a `Vector(N, T)` lane count to a positive compile-time integer
|
||||
/// through the shared `evalConstIntExpr` folder — so a literal (`Vector(4,
|
||||
/// f32)`), a module/generic const (`Vector(N, f32)`), and a const expression
|
||||
/// (`Vector(M + 1, f32)`) all resolve identically. A non-const lane
|
||||
/// (`Vector(get(), f32)`) or a non-positive one emits a clean diagnostic and
|
||||
/// returns null; the caller yields `.unresolved` rather than fabricating a
|
||||
/// `<0 x float>` lane count that crashes LLVM verification.
|
||||
fn resolveVectorLane(self: *Lowering, lane_node: *const Node) ?u32 {
|
||||
const v = program_index_mod.evalConstIntExpr(lane_node, self);
|
||||
if (v == null or v.? < 1) {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, lane_node.span, "Vector lane count must be a positive compile-time integer constant", .{});
|
||||
return null;
|
||||
}
|
||||
return @intCast(v.?);
|
||||
}
|
||||
|
||||
/// Resolve a generic value-param argument (`$N: u32`) to its compile-time
|
||||
/// integer through the shared `evalConstIntExpr` folder, so a module/generic
|
||||
/// const arg (`Vec(N, f32)`) binds the same value — and mangles to the same
|
||||
/// instantiation — a literal (`Vec(3, f32)`) would. A non-const arg emits a
|
||||
/// clean diagnostic and returns null; the caller bails rather than
|
||||
/// fabricating a 0 binding under a wrong mangled name.
|
||||
fn resolveValueParamArg(self: *Lowering, arg_node: *const Node) ?i64 {
|
||||
if (program_index_mod.evalConstIntExpr(arg_node, self)) |v| return v;
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, arg_node.span, "generic value parameter must be a compile-time integer constant", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolve a .call node that represents a type constructor (e.g., List(T), Vector(N, T)).
|
||||
fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId {
|
||||
const callee_name: []const u8 = switch (cl.callee.data) {
|
||||
@@ -11837,16 +11858,7 @@ pub const Lowering = struct {
|
||||
};
|
||||
// Built-in: Vector(N, T)
|
||||
if (std.mem.eql(u8, callee_name, "Vector") and cl.args.len == 2) {
|
||||
const length: u32 = switch (cl.args[0].data) {
|
||||
.int_literal => |lit| @intCast(@as(u64, @bitCast(lit.value))),
|
||||
.identifier => |id| blk: {
|
||||
if (self.comptime_value_bindings) |cvb| {
|
||||
if (cvb.get(id.name)) |v| break :blk @intCast(@as(u64, @bitCast(v)));
|
||||
}
|
||||
break :blk 0;
|
||||
},
|
||||
else => 0,
|
||||
};
|
||||
const length = self.resolveVectorLane(cl.args[0]) orelse return .unresolved;
|
||||
const elem = self.resolveTypeWithBindings(cl.args[1]);
|
||||
return self.module.types.vectorOf(elem, length);
|
||||
}
|
||||
@@ -11878,24 +11890,7 @@ pub const Lowering = struct {
|
||||
// Vector(N, T) — built-in parameterized type
|
||||
if (std.mem.eql(u8, base_name, "Vector")) {
|
||||
if (pt.args.len == 2) {
|
||||
// Resolve length: literal, or bound comptime value
|
||||
const length: u32 = switch (pt.args[0].data) {
|
||||
.int_literal => |lit| @intCast(@as(u64, @bitCast(lit.value))),
|
||||
.identifier => |id| blk: {
|
||||
if (self.comptime_value_bindings) |cvb| {
|
||||
if (cvb.get(id.name)) |v| break :blk @intCast(@as(u64, @bitCast(v)));
|
||||
}
|
||||
break :blk 0;
|
||||
},
|
||||
.type_expr => |te| blk: {
|
||||
if (self.comptime_value_bindings) |cvb| {
|
||||
if (cvb.get(te.name)) |v| break :blk @intCast(@as(u64, @bitCast(v)));
|
||||
}
|
||||
break :blk 0;
|
||||
},
|
||||
else => 0,
|
||||
};
|
||||
// Resolve element type through bindings
|
||||
const length = self.resolveVectorLane(pt.args[0]) orelse return .unresolved;
|
||||
const elem = self.resolveTypeWithBindings(pt.args[1]);
|
||||
return table.vectorOf(elem, length);
|
||||
}
|
||||
@@ -11974,11 +11969,8 @@ pub const Lowering = struct {
|
||||
const tname = self.formatTypeName(ty);
|
||||
name_parts.appendSlice(self.alloc, tname) catch {};
|
||||
} else {
|
||||
// Value param (e.g., $N: u32) — extract integer
|
||||
const val: i64 = switch (args[i].data) {
|
||||
.int_literal => |lit| lit.value,
|
||||
else => 0,
|
||||
};
|
||||
// Value param (e.g., $N: u32) — fold to a compile-time integer.
|
||||
const val = self.resolveValueParamArg(args[i]) orelse return .unresolved;
|
||||
cvb.put(tp.name, val) catch {};
|
||||
var val_buf: [32]u8 = undefined;
|
||||
const val_str = std.fmt.bufPrint(&val_buf, "{d}", .{val}) catch "0";
|
||||
@@ -12071,10 +12063,8 @@ pub const Lowering = struct {
|
||||
const tname = self.formatTypeName(ty);
|
||||
name_parts.appendSlice(self.alloc, tname) catch {};
|
||||
} else {
|
||||
const val: i64 = switch (args[i].data) {
|
||||
.int_literal => |lit| lit.value,
|
||||
else => 0,
|
||||
};
|
||||
// Value param (e.g., $N: u32) — fold to a compile-time integer.
|
||||
const val = self.resolveValueParamArg(args[i]) orelse return null;
|
||||
cvb.put(tp.name, val) catch {};
|
||||
var val_buf: [32]u8 = undefined;
|
||||
const val_str = std.fmt.bufPrint(&val_buf, "{d}", .{val}) catch "0";
|
||||
|
||||
Reference in New Issue
Block a user