lang F1: range-based for + inline-for unroll over packs
Add range loop syntax:
- runtime for start..end (i) { } counting loop, cursor optional, end exclusive
- comptime inline for start..end (i) { } comptime-unrolled body
The inline form binds the cursor as an int_val comptime constant per
iteration, so xs[i] over a heterogeneous pack substitutes the concrete
per-position element -- the canonical's pack-iteration vehicle
(inline for 0..sources.len (i) { sources[i].addListener(...) }).
- AST: ForExpr.range_end, ForExpr.is_inline
- parser: parseForExpr range vs collection form; suppress_call flag so
N (i) is not read as a call N(i) while parsing a range bound
- lower: lowerRuntimeRangeFor / lowerInlineRangeFor; evalComptimeInt;
comptimeIndexOf extends pack-index resolution beyond int literals
Revises spec's inline for i in 0..N to the no-in, range-first, paren-cursor
form. Regression: examples/200-for-range.sx.
This commit is contained in:
177
src/ir/lower.zig
177
src/ir/lower.zig
@@ -3201,6 +3201,11 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
if (fe.range_end) |end_node| {
|
||||
if (fe.is_inline) return self.lowerInlineRangeFor(fe, end_node);
|
||||
return self.lowerRuntimeRangeFor(fe, end_node);
|
||||
}
|
||||
|
||||
// Lower iterable
|
||||
const iterable = self.lowerExpr(fe.iterable);
|
||||
|
||||
@@ -3277,6 +3282,145 @@ pub const Lowering = struct {
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
|
||||
/// Runtime counting loop `for start..end (i) { }` — `i` (optional) is the
|
||||
/// cursor, `end` is exclusive. Lowers to the same header/inc/exit shape as
|
||||
/// the collection form, minus the element fetch.
|
||||
fn lowerRuntimeRangeFor(self: *Lowering, fe: *const ast.ForExpr, end_node: *Node) Ref {
|
||||
const start = self.lowerExpr(fe.iterable);
|
||||
const end = self.lowerExpr(end_node);
|
||||
|
||||
const idx_slot = self.builder.alloca(.s64);
|
||||
self.builder.store(idx_slot, start);
|
||||
|
||||
const header_bb = self.freshBlock("for.hdr");
|
||||
const body_bb = self.freshBlock("for.body");
|
||||
const inc_bb = self.freshBlock("for.inc");
|
||||
const exit_bb = self.freshBlock("for.exit");
|
||||
|
||||
self.builder.br(header_bb, &.{});
|
||||
|
||||
self.builder.switchToBlock(header_bb);
|
||||
const idx_val = self.builder.load(idx_slot, .s64);
|
||||
const cmp = self.builder.cmpLt(idx_val, end);
|
||||
self.builder.condBr(cmp, body_bb, &.{}, exit_bb, &.{});
|
||||
|
||||
self.builder.switchToBlock(body_bb);
|
||||
var body_scope = Scope.init(self.alloc, self.scope);
|
||||
const old_scope = self.scope;
|
||||
self.scope = &body_scope;
|
||||
if (fe.capture_name.len > 0) {
|
||||
body_scope.put(fe.capture_name, .{ .ref = idx_val, .ty = .s64, .is_alloca = false });
|
||||
}
|
||||
|
||||
const old_break = self.break_target;
|
||||
const old_continue = self.continue_target;
|
||||
self.break_target = exit_bb;
|
||||
self.continue_target = inc_bb;
|
||||
|
||||
self.lowerBlock(fe.body);
|
||||
|
||||
self.break_target = old_break;
|
||||
self.continue_target = old_continue;
|
||||
self.scope = old_scope;
|
||||
body_scope.deinit();
|
||||
|
||||
if (!self.currentBlockHasTerminator()) {
|
||||
self.builder.br(inc_bb, &.{});
|
||||
}
|
||||
|
||||
self.builder.switchToBlock(inc_bb);
|
||||
{
|
||||
const cur_idx = self.builder.load(idx_slot, .s64);
|
||||
const one = self.builder.constInt(1, .s64);
|
||||
const next_idx = self.builder.add(cur_idx, one, .s64);
|
||||
self.builder.store(idx_slot, next_idx);
|
||||
self.builder.br(header_bb, &.{});
|
||||
}
|
||||
|
||||
self.builder.switchToBlock(exit_bb);
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
|
||||
/// Comptime-unrolled `inline for start..end (i) { }`. `start`/`end` must be
|
||||
/// comptime-known. The body is lowered `end - start` times with the cursor
|
||||
/// bound as an `int_val` comptime constant, so `xs[i]` over a pack
|
||||
/// substitutes the concrete per-position argument each iteration.
|
||||
fn lowerInlineRangeFor(self: *Lowering, fe: *const ast.ForExpr, end_node: *Node) Ref {
|
||||
const start = self.evalComptimeInt(fe.iterable) orelse {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, fe.iterable.span, "inline for: range start is not a compile-time integer", .{});
|
||||
return self.builder.constInt(0, .void);
|
||||
};
|
||||
const end = self.evalComptimeInt(end_node) orelse {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, end_node.span, "inline for: range end is not a compile-time integer", .{});
|
||||
return self.builder.constInt(0, .void);
|
||||
};
|
||||
|
||||
var i: i64 = start;
|
||||
while (i < end) : (i += 1) {
|
||||
var body_scope = Scope.init(self.alloc, self.scope);
|
||||
const old_scope = self.scope;
|
||||
self.scope = &body_scope;
|
||||
|
||||
// Bind the cursor both as a runtime value (constInt, for uses like
|
||||
// `print(i)`) and as a comptime constant (for `xs[i]` substitution).
|
||||
var had_prev = false;
|
||||
var prev: ComptimeValue = undefined;
|
||||
if (fe.capture_name.len > 0) {
|
||||
body_scope.put(fe.capture_name, .{ .ref = self.builder.constInt(i, .s64), .ty = .s64, .is_alloca = false });
|
||||
if (self.comptime_constants.get(fe.capture_name)) |p| {
|
||||
had_prev = true;
|
||||
prev = p;
|
||||
}
|
||||
self.comptime_constants.put(fe.capture_name, .{ .int_val = i }) catch {};
|
||||
}
|
||||
|
||||
self.lowerBlock(fe.body);
|
||||
|
||||
if (fe.capture_name.len > 0) {
|
||||
if (had_prev) {
|
||||
self.comptime_constants.put(fe.capture_name, prev) catch {};
|
||||
} else {
|
||||
_ = self.comptime_constants.remove(fe.capture_name);
|
||||
}
|
||||
}
|
||||
|
||||
self.scope = old_scope;
|
||||
body_scope.deinit();
|
||||
|
||||
if (self.currentBlockHasTerminator()) break;
|
||||
}
|
||||
|
||||
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).
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn lowerMatch(self: *Lowering, me: *const ast.MatchExpr) Ref {
|
||||
// inline if match: evaluate at compile time, only lower the matching arm
|
||||
if (me.is_comptime) {
|
||||
@@ -4664,14 +4808,32 @@ pub const Lowering = struct {
|
||||
const pan = self.pack_arg_nodes orelse return null;
|
||||
if (ie.object.data != .identifier) return null;
|
||||
const arg_nodes = pan.get(ie.object.data.identifier.name) orelse return null;
|
||||
if (ie.index.data != .int_literal) return null;
|
||||
const raw: i64 = ie.index.data.int_literal.value;
|
||||
const raw: i64 = self.comptimeIndexOf(ie.index) orelse return null;
|
||||
if (raw < 0) return null;
|
||||
const i: usize = @intCast(raw);
|
||||
if (i >= arg_nodes.len) return null;
|
||||
return arg_nodes[i];
|
||||
}
|
||||
|
||||
/// Resolve an index expression to a comptime-known integer: a literal,
|
||||
/// or an identifier bound to an `int_val` in `comptime_constants` (e.g.
|
||||
/// the cursor of an `inline for 0..N (i)` unroll). Otherwise null.
|
||||
fn comptimeIndexOf(self: *Lowering, index: *const Node) ?i64 {
|
||||
switch (index.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;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
|
||||
fn lowerSliceExpr(self: *Lowering, se: *const ast.SliceExpr) Ref {
|
||||
const obj = self.lowerExpr(se.object);
|
||||
const lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .s64);
|
||||
@@ -12882,12 +13044,13 @@ pub const Lowering = struct {
|
||||
// would otherwise lose the type when the mono's
|
||||
// scope isn't set up yet (generic-`$R` pre-inference).
|
||||
if (self.pack_arg_types) |pat| {
|
||||
if (ie.object.data == .identifier and ie.index.data == .int_literal) {
|
||||
if (ie.object.data == .identifier) {
|
||||
if (pat.get(ie.object.data.identifier.name)) |arg_tys| {
|
||||
const raw: i64 = ie.index.data.int_literal.value;
|
||||
if (raw >= 0) {
|
||||
const i: usize = @intCast(raw);
|
||||
if (i < arg_tys.len) return arg_tys[i];
|
||||
if (self.comptimeIndexOf(ie.index)) |raw| {
|
||||
if (raw >= 0) {
|
||||
const i: usize = @intCast(raw);
|
||||
if (i < arg_tys.len) return arg_tys[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user