Skip to content

Commit b4da8ee

Browse files
committed
Zir: split up start and end of range in for_len
The old lowering was kind of neat, but it unintentionally allowed the syntax `for (123) |_| { ... }`, and there wasn't really a way to fix that. So, instead, we include both the start and the end of the range in the `for_len` instruction (each operand to `for` now has *two* entries in this multi-op instruction). This slightly increases the size of ZIR for loops of predominantly indexables, but the difference is small enough that it's not worth complicating ZIR to try and fix it.
1 parent 252c203 commit b4da8ee

File tree

4 files changed

+42
-42
lines changed

4 files changed

+42
-42
lines changed

lib/std/zig/AstGen.zig

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7020,7 +7020,7 @@ fn forExpr(
70207020
const indexables = try gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len);
70217021
defer gpa.free(indexables);
70227022
// elements of this array can be `none`, indicating no length check.
7023-
const lens = try gpa.alloc(Zir.Inst.Ref, for_full.ast.inputs.len);
7023+
const lens = try gpa.alloc([2]Zir.Inst.Ref, for_full.ast.inputs.len);
70247024
defer gpa.free(lens);
70257025

70267026
// We will use a single zero-based counter no matter how many indexables there are.
@@ -7039,7 +7039,7 @@ fn forExpr(
70397039

70407040
{
70417041
var capture_token = for_full.payload_token;
7042-
for (for_full.ast.inputs, indexables, lens) |input, *indexable_ref, *len_ref| {
7042+
for (for_full.ast.inputs, indexables, lens) |input, *indexable_ref, *len_refs| {
70437043
const capture_is_ref = token_tags[capture_token] == .asterisk;
70447044
const ident_tok = capture_token + @intFromBool(capture_is_ref);
70457045
const is_discard = mem.eql(u8, tree.tokenSlice(ident_tok), "_");
@@ -7068,24 +7068,21 @@ fn forExpr(
70687068
try astgen.appendErrorTok(ident_tok, "discard of unbounded counter", .{});
70697069
}
70707070

7071-
const start_is_zero = nodeIsTriviallyZero(tree, start_node);
7072-
const range_len = if (end_val == .none or start_is_zero)
7073-
end_val
7074-
else
7075-
try parent_gz.addPlNode(.sub, input, Zir.Inst.Bin{
7076-
.lhs = end_val,
7077-
.rhs = start_val,
7078-
});
7071+
if (end_val == .none) {
7072+
len_refs.* = .{ .none, .none };
7073+
} else {
7074+
any_len_checks = true;
7075+
len_refs.* = .{ start_val, end_val };
7076+
}
70797077

7080-
any_len_checks = any_len_checks or range_len != .none;
7078+
const start_is_zero = nodeIsTriviallyZero(tree, start_node);
70817079
indexable_ref.* = if (start_is_zero) .none else start_val;
7082-
len_ref.* = range_len;
70837080
} else {
70847081
const indexable = try expr(parent_gz, scope, .{ .rl = .none }, input);
70857082

70867083
any_len_checks = true;
70877084
indexable_ref.* = indexable;
7088-
len_ref.* = indexable;
7085+
len_refs.* = .{ indexable, .none };
70897086
}
70907087
}
70917088
}
@@ -7097,12 +7094,13 @@ fn forExpr(
70977094
// We use a dedicated ZIR instruction to assert the lengths to assist with
70987095
// nicer error reporting as well as fewer ZIR bytes emitted.
70997096
const len: Zir.Inst.Ref = len: {
7100-
const lens_len: u32 = @intCast(lens.len);
7097+
const all_lens = @as([*]Zir.Inst.Ref, @ptrCast(lens))[0 .. lens.len * 2];
7098+
const lens_len: u32 = @intCast(all_lens.len);
71017099
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.MultiOp).@"struct".fields.len + lens_len);
71027100
const len = try parent_gz.addPlNode(.for_len, node, Zir.Inst.MultiOp{
71037101
.operands_len = lens_len,
71047102
});
7105-
appendRefsAssumeCapacity(astgen, lens);
7103+
appendRefsAssumeCapacity(astgen, all_lens);
71067104
break :len len;
71077105
};
71087106

lib/std/zig/Zir.zig

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,10 @@ pub const Inst = struct {
526526
/// Asserts that all the lengths provided match. Used to build a for loop.
527527
/// Return value is the length as a usize.
528528
/// Uses the `pl_node` field with payload `MultiOp`.
529-
/// There is exactly one item corresponding to each AST node inside the for
530-
/// loop condition. Any item may be `none`, indicating an unbounded range.
529+
/// There are two items for each AST node inside the for loop condition.
530+
/// If both items in a pair are `.none`, then this node is an unbounded range.
531+
/// If only the second item in a pair is `.none`, then the first is an indexable.
532+
/// Otherwise, the node is a bounded range `a..b`, with the items being `a` and `b`.
531533
/// Illegal behaviors:
532534
/// * If all lengths are unbounded ranges (always a compile error).
533535
/// * If any two lengths do not match each other.

src/Sema.zig

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4356,35 +4356,33 @@ fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
43564356
const ip = &zcu.intern_pool;
43574357
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
43584358
const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
4359-
const args = sema.code.refSlice(extra.end, extra.data.operands_len);
4359+
const all_args = sema.code.refSlice(extra.end, extra.data.operands_len);
4360+
const arg_pairs: []const [2]Zir.Inst.Ref = @as([*]const [2]Zir.Inst.Ref, @ptrCast(all_args))[0..@divExact(all_args.len, 2)];
43604361
const src = block.nodeOffset(inst_data.src_node);
43614362

43624363
var len: Air.Inst.Ref = .none;
43634364
var len_val: ?Value = null;
43644365
var len_idx: u32 = undefined;
43654366
var any_runtime = false;
43664367

4367-
const runtime_arg_lens = try gpa.alloc(Air.Inst.Ref, args.len);
4368+
const runtime_arg_lens = try gpa.alloc(Air.Inst.Ref, arg_pairs.len);
43684369
defer gpa.free(runtime_arg_lens);
43694370

43704371
// First pass to look for comptime values.
4371-
for (args, 0..) |zir_arg, i_usize| {
4372+
for (arg_pairs, 0..) |zir_arg_pair, i_usize| {
43724373
const i: u32 = @intCast(i_usize);
43734374
runtime_arg_lens[i] = .none;
4374-
if (zir_arg == .none) continue;
4375-
const object = try sema.resolveInst(zir_arg);
4376-
const object_ty = sema.typeOf(object);
4377-
// Each arg could be an indexable, or a range, in which case the length
4378-
// is passed directly as an integer.
4379-
const is_int = switch (object_ty.zigTypeTag(zcu)) {
4380-
.int, .comptime_int => true,
4381-
else => false,
4382-
};
4375+
if (zir_arg_pair[0] == .none) continue;
4376+
43834377
const arg_src = block.src(.{ .for_input = .{
43844378
.for_node_offset = inst_data.src_node,
43854379
.input_index = i,
43864380
} });
4387-
const arg_len_uncoerced = if (is_int) object else l: {
4381+
4382+
const arg_len_uncoerced = if (zir_arg_pair[1] == .none) l: {
4383+
// This argument is an indexable.
4384+
const object = try sema.resolveInst(zir_arg_pair[0]);
4385+
const object_ty = sema.typeOf(object);
43884386
if (!object_ty.isIndexable(zcu)) {
43894387
// Instead of using checkIndexable we customize this error.
43904388
const msg = msg: {
@@ -4401,8 +4399,12 @@ fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
44014399
return sema.failWithOwnedErrorMsg(block, msg);
44024400
}
44034401
if (!object_ty.indexableHasLen(zcu)) continue;
4404-
44054402
break :l try sema.fieldVal(block, arg_src, object, try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls), arg_src);
4403+
} else l: {
4404+
// This argument is a range.
4405+
const range_start = try sema.resolveInst(zir_arg_pair[0]);
4406+
const range_end = try sema.resolveInst(zir_arg_pair[1]);
4407+
break :l try sema.analyzeArithmetic(block, .sub, range_end, range_start, arg_src, arg_src, arg_src, true);
44064408
};
44074409
const arg_len = try sema.coerce(block, Type.usize, arg_len_uncoerced, arg_src);
44084410
if (len == .none) {
@@ -4444,17 +4446,12 @@ fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
44444446
const msg = msg: {
44454447
const msg = try sema.errMsg(src, "unbounded for loop", .{});
44464448
errdefer msg.destroy(gpa);
4447-
for (args, 0..) |zir_arg, i_usize| {
4449+
for (arg_pairs, 0..) |zir_arg_pair, i_usize| {
44484450
const i: u32 = @intCast(i_usize);
4449-
if (zir_arg == .none) continue;
4450-
const object = try sema.resolveInst(zir_arg);
4451+
if (zir_arg_pair[0] == .none) continue;
4452+
if (zir_arg_pair[1] != .none) continue;
4453+
const object = try sema.resolveInst(zir_arg_pair[0]);
44514454
const object_ty = sema.typeOf(object);
4452-
// Each arg could be an indexable, or a range, in which case the length
4453-
// is passed directly as an integer.
4454-
switch (object_ty.zigTypeTag(zcu)) {
4455-
.int, .comptime_int => continue,
4456-
else => {},
4457-
}
44584455
const arg_src = block.src(.{ .for_input = .{
44594456
.for_node_offset = inst_data.src_node,
44604457
.input_index = i,

test/cases/compile_errors/for.zig

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ export fn d() void {
2828
_ = x3;
2929
}
3030
}
31+
export fn e() void {
32+
for (123) |_| {}
33+
}
3134

3235
// error
33-
// backend=stage2
34-
// target=native
3536
//
3637
// :2:5: error: non-matching for loop lengths
3738
// :2:11: note: length 10 here
@@ -43,3 +44,5 @@ export fn d() void {
4344
// :25:5: error: unbounded for loop
4445
// :25:10: note: type '[*]const u8' has no upper bound
4546
// :25:18: note: type '[*]const u8' has no upper bound
47+
// :32:10: error: type 'comptime_int' is not indexable and not a range
48+
// :32:10: note: for loop operand must be a range, array, slice, tuple, or vector

0 commit comments

Comments
 (0)