authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-27 15:26:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-27 15:30:31-07:00
log70894d5c2f6032b0d0be1fa00a30554b7e6db2d6
treef54e4c5757e41dd1add4aa61ff60a6e6923087d6
parent2c23699594348d5707d6cc9cd591cc8c28fa4605

AstGen: fix loop result locations

The main problem was that the loop body was treated as an expression that was one of the peer result values of a loop, when in reality the loop body is noreturn and only the `break` operands are the result values of loops. This was solved by introducing an override that prevents rvalue() from emitting a store to result location instruction for loop bodies. An orthogonal change also included in this commit is switching `elem_val` index expressions to using `coerced_ty` and doing the coercion to `usize` inside `Sema`, resulting in smaller ZIR (since the cast becomes implied). I also changed the break operand expression to use `reachableExpr`, introducing a new compile error for double break. This makes a few more behavior tests pass for `while` and `for` loops.

9 files changed, 125 insertions(+), 88 deletions(-)

src/AstGen.zig+64-31
......@@ -1709,23 +1709,42 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
17091709 return Zir.Inst.Ref.unreachable_value;
17101710 }
17111711 block_gz.break_count += 1;
1712 const operand = try expr(parent_gz, parent_scope, block_gz.break_result_loc, rhs);
1713 // if list grew as much as rvalue_rl_count, then a break inside operand already saved the store_to_block_ptr
1714 const have_store_to_block = block_gz.rvalue_rl_count > block_gz.labeled_store_to_block_ptr_list.items.len;
1715
1716 const br = try parent_gz.addBreak(.@"break", block_inst, operand);
1717
1718 if (block_gz.break_result_loc == .block_ptr) {
1719 try block_gz.labeled_breaks.append(astgen.gpa, br);
1720
1721 if (have_store_to_block) {
1722 const zir_tags = parent_gz.astgen.instructions.items(.tag);
1723 const zir_datas = parent_gz.astgen.instructions.items(.data);
1724 const store_inst = @intCast(u32, zir_tags.len - 2);
1725 assert(zir_tags[store_inst] == .store_to_block_ptr);
1726 assert(zir_datas[store_inst].bin.lhs == block_gz.rl_ptr);
1727 try block_gz.labeled_store_to_block_ptr_list.append(astgen.gpa, store_inst);
1728 }
1712
1713 // The loop scope has a mechanism to prevent rvalue() from emitting a
1714 // store to the result location for the loop body (since it is continues
1715 // rather than returning a result from the loop) but here is a `break`
1716 // which needs to override this behavior.
1717 const prev_rvalue_noresult = parent_gz.rvalue_noresult;
1718 parent_gz.rvalue_noresult = .none;
1719 const operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_loc, rhs, node);
1720 parent_gz.rvalue_noresult = prev_rvalue_noresult;
1721
1722 switch (block_gz.break_result_loc) {
1723 .block_ptr => {
1724 const br = try parent_gz.addBreak(.@"break", block_inst, operand);
1725 try block_gz.labeled_breaks.append(astgen.gpa, br);
1726
1727 // if list grew as much as rvalue_rl_count, then a break
1728 // inside operand already saved the store_to_block_ptr
1729 const have_store_to_block = block_gz.rvalue_rl_count >
1730 block_gz.labeled_store_to_block_ptr_list.items.len;
1731 if (have_store_to_block) {
1732 const zir_tags = parent_gz.astgen.instructions.items(.tag);
1733 const zir_datas = parent_gz.astgen.instructions.items(.data);
1734 const store_inst = @intCast(u32, zir_tags.len - 2);
1735 assert(zir_tags[store_inst] == .store_to_block_ptr);
1736 assert(zir_datas[store_inst].bin.lhs == block_gz.rl_ptr);
1737 try block_gz.labeled_store_to_block_ptr_list.append(astgen.gpa, store_inst);
1738 }
1739 },
1740 .ptr => {
1741 // In this case we don't have any mechanism to intercept it;
1742 // we assume the result location is written, and we break with void.
1743 _ = try parent_gz.addBreak(.@"break", block_inst, .void_value);
1744 },
1745 else => {
1746 _ = try parent_gz.addBreak(.@"break", block_inst, operand);
1747 },
17291748 }
17301749 return Zir.Inst.Ref.unreachable_value;
17311750 },
......@@ -4776,7 +4795,7 @@ fn arrayAccess(
47764795 else => return rvalue(gz, rl, try gz.addBin(
47774796 .elem_val,
47784797 try expr(gz, scope, .none, node_datas[node].lhs),
4779 try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs),
4798 try expr(gz, scope, .{ .coerced_ty = .usize_type }, node_datas[node].rhs),
47804799 ), node),
47814800 }
47824801}
......@@ -5183,6 +5202,7 @@ fn whileExpr(
51835202 // make scope now but don't stack on parent_gz until loop_scope
51845203 // gets unstacked after cont_expr is emitted and added below
51855204 var then_scope = parent_gz.makeSubBlock(&continue_scope.base);
5205 then_scope.markAsLoopBody(loop_scope);
51865206 then_scope.instructions_top = GenZir.unstacked_top;
51875207 defer then_scope.unstack();
51885208
......@@ -5267,9 +5287,6 @@ fn whileExpr(
52675287 then_scope.instructions_top = then_scope.instructions.items.len;
52685288 if (payload_inst != 0) try then_scope.instructions.append(astgen.gpa, payload_inst);
52695289 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);
5270 if (!then_scope.endsWithNoReturn()) {
5271 loop_scope.break_count += 1;
5272 }
52735290 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
52745291
52755292 var else_scope = parent_gz.makeSubBlock(&continue_scope.base);
......@@ -5426,6 +5443,7 @@ fn forExpr(
54265443 }
54275444
54285445 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);
5446 then_scope.markAsLoopBody(loop_scope);
54295447 defer then_scope.unstack();
54305448
54315449 var payload_val_scope: Scope.LocalVal = undefined;
......@@ -5482,9 +5500,6 @@ fn forExpr(
54825500 };
54835501
54845502 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);
5485 if (!then_scope.endsWithNoReturn()) {
5486 loop_scope.break_count += 1;
5487 }
54885503 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
54895504
54905505 var else_scope = parent_gz.makeSubBlock(&cond_scope.base);
......@@ -8369,19 +8384,25 @@ fn rvalue(
83698384 }
83708385 },
83718386 .ptr => |ptr_inst| {
8372 _ = try gz.addPlNode(.store_node, src_node, Zir.Inst.Bin{
8373 .lhs = ptr_inst,
8374 .rhs = result,
8375 });
8387 if (gz.rvalue_noresult != ptr_inst) {
8388 _ = try gz.addPlNode(.store_node, src_node, Zir.Inst.Bin{
8389 .lhs = ptr_inst,
8390 .rhs = result,
8391 });
8392 }
83768393 return result;
83778394 },
83788395 .inferred_ptr => |alloc| {
8379 _ = try gz.addBin(.store_to_inferred_ptr, alloc, result);
8396 if (gz.rvalue_noresult != alloc) {
8397 _ = try gz.addBin(.store_to_inferred_ptr, alloc, result);
8398 }
83808399 return result;
83818400 },
83828401 .block_ptr => |block_scope| {
8383 block_scope.rvalue_rl_count += 1;
8384 _ = try gz.addBin(.store_to_block_ptr, block_scope.rl_ptr, result);
8402 if (gz.rvalue_noresult != block_scope.rl_ptr) {
8403 block_scope.rvalue_rl_count += 1;
8404 _ = try gz.addBin(.store_to_block_ptr, block_scope.rl_ptr, result);
8405 }
83858406 return result;
83868407 },
83878408 }
......@@ -8890,6 +8911,7 @@ const GenZir = struct {
88908911 rl_ptr: Zir.Inst.Ref = .none,
88918912 /// When a block has a type result location, here it is.
88928913 rl_ty_inst: Zir.Inst.Ref = .none,
8914 rvalue_noresult: Zir.Inst.Ref = .none,
88938915 /// Keeps track of how many branches of a block did not actually
88948916 /// consume the result location. astgen uses this to figure out
88958917 /// whether to rely on break instructions or writing to the result
......@@ -10096,6 +10118,17 @@ const GenZir = struct {
1009610118 }
1009710119 }
1009810120 }
10121
10122 /// Control flow does not fall through the "then" block of a loop; it continues
10123 /// back to the while condition. This prevents `rvalue` from
10124 /// adding an invalid store to the result location of `then_scope`.
10125 fn markAsLoopBody(gz: *GenZir, loop_scope: GenZir) void {
10126 gz.rvalue_noresult = switch (loop_scope.break_result_loc) {
10127 .ptr, .inferred_ptr => |ptr| ptr,
10128 .block_ptr => |block| block.rl_ptr,
10129 else => .none,
10130 };
10131 }
1009910132};
1010010133
1010110134/// This can only be for short-lived references; the memory becomes invalidated
src/Sema.zig+3-1
......@@ -12528,7 +12528,7 @@ fn elemVal(
1252812528 block: *Block,
1252912529 src: LazySrcLoc,
1253012530 array: Air.Inst.Ref,
12531 elem_index: Air.Inst.Ref,
12531 elem_index_uncasted: Air.Inst.Ref,
1253212532 elem_index_src: LazySrcLoc,
1253312533) CompileError!Air.Inst.Ref {
1253412534 const array_src = src; // TODO better source location
......@@ -12538,6 +12538,8 @@ fn elemVal(
1253812538 return sema.fail(block, src, "array access of non-indexable type '{}'", .{array_ty});
1253912539 }
1254012540
12541 const elem_index = try sema.coerce(block, Type.usize, elem_index_uncasted, elem_index_src);
12542
1254112543 switch (array_ty.zigTypeTag()) {
1254212544 .Pointer => switch (array_ty.ptrSize()) {
1254312545 .Slice => {
test/behavior.zig-1
......@@ -145,7 +145,6 @@ test {
145145 _ = @import("behavior/bugs/7027.zig");
146146 _ = @import("behavior/bugs/7047.zig");
147147 _ = @import("behavior/bugs/9584.zig");
148 _ = @import("behavior/bugs/9967.zig");
149148 _ = @import("behavior/bugs/10147.zig");
150149 _ = @import("behavior/byteswap.zig");
151150 _ = @import("behavior/call_stage1.zig");
test/behavior/bugs/9967.zig deleted-8
......@@ -1,8 +0,0 @@
1const std = @import("std");
2
3test "nested breaks to same labeled block" {
4 const a = blk: {
5 break :blk break :blk @as(u32, 1);
6 };
7 try std.testing.expectEqual(a, 1);
8}
test/behavior/for.zig+17
......@@ -116,3 +116,20 @@ test "for with null and T peer types and inferred result location type" {
116116 try S.doTheTest(&[_]u8{ 1, 2 });
117117 comptime try S.doTheTest(&[_]u8{ 1, 2 });
118118}
119
120test "2 break statements and an else" {
121 const S = struct {
122 fn entry(t: bool, f: bool) !void {
123 var buf: [10]u8 = undefined;
124 var ok = false;
125 ok = for (buf) |item| {
126 _ = item;
127 if (f) break false;
128 if (t) break true;
129 } else false;
130 try expect(ok);
131 }
132 };
133 try S.entry(true, false);
134 comptime try S.entry(true, false);
135}
test/behavior/for_stage1.zig-17
......@@ -26,23 +26,6 @@ fn mangleString(s: []u8) void {
2626 }
2727}
2828
29test "2 break statements and an else" {
30 const S = struct {
31 fn entry(t: bool, f: bool) !void {
32 var buf: [10]u8 = undefined;
33 var ok = false;
34 ok = for (buf) |item| {
35 _ = item;
36 if (f) break false;
37 if (t) break true;
38 } else false;
39 try expect(ok);
40 }
41 };
42 try S.entry(true, false);
43 comptime try S.entry(true, false);
44}
45
4629test "for copies its payload" {
4730 const S = struct {
4831 fn doTheTest() !void {
test/behavior/while.zig+30
......@@ -236,3 +236,33 @@ test "while on error union with else result follow break prong" {
236236 } else |_| @as(i32, 2);
237237 try expect(result == 10);
238238}
239
240test "while bool 2 break statements and an else" {
241 const S = struct {
242 fn entry(t: bool, f: bool) !void {
243 var ok = false;
244 ok = while (t) {
245 if (f) break false;
246 if (t) break true;
247 } else false;
248 try expect(ok);
249 }
250 };
251 try S.entry(true, false);
252 comptime try S.entry(true, false);
253}
254
255test "while optional 2 break statements and an else" {
256 const S = struct {
257 fn entry(opt_t: ?bool, f: bool) !void {
258 var ok = false;
259 ok = while (opt_t) |t| {
260 if (f) break false;
261 if (t) break true;
262 } else false;
263 try expect(ok);
264 }
265 };
266 try S.entry(true, false);
267 comptime try S.entry(true, false);
268}
test/behavior/while_stage1.zig-30
......@@ -1,36 +1,6 @@
11const std = @import("std");
22const expect = std.testing.expect;
33
4test "while bool 2 break statements and an else" {
5 const S = struct {
6 fn entry(t: bool, f: bool) !void {
7 var ok = false;
8 ok = while (t) {
9 if (f) break false;
10 if (t) break true;
11 } else false;
12 try expect(ok);
13 }
14 };
15 try S.entry(true, false);
16 comptime try S.entry(true, false);
17}
18
19test "while optional 2 break statements and an else" {
20 const S = struct {
21 fn entry(opt_t: ?bool, f: bool) !void {
22 var ok = false;
23 ok = while (opt_t) |t| {
24 if (f) break false;
25 if (t) break true;
26 } else false;
27 try expect(ok);
28 }
29 };
30 try S.entry(true, false);
31 comptime try S.entry(true, false);
32}
33
344test "while error 2 break statements and an else" {
355 const S = struct {
366 fn entry(opt_t: anyerror!bool, f: bool) !void {
test/compile_errors.zig+11
......@@ -5042,6 +5042,17 @@ pub fn addCases(ctx: *TestContext) !void {
50425042 "tmp.zig:2:12: note: control flow is diverted here",
50435043 });
50445044
5045 ctx.objErrStage1("unreachable code - double break",
5046 \\export fn a() void {
5047 \\ const b = blk: {
5048 \\ break :blk break :blk @as(u32, 1);
5049 \\ };
5050 \\}
5051 , &[_][]const u8{
5052 "tmp.zig:3:9: error: unreachable code",
5053 "tmp.zig:3:20: note: control flow is diverted here",
5054 });
5055
50455056 ctx.objErrStage1("chained comparison operators",
50465057 \\export fn a(value: u32) bool {
50475058 \\ return 1 < value < 1000;