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...@@ -1709,23 +1709,42 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
1709 return Zir.Inst.Ref.unreachable_value;1709 return Zir.Inst.Ref.unreachable_value;
1710 }1710 }
1711 block_gz.break_count += 1;1711 block_gz.break_count += 1;
1712 const operand = try expr(parent_gz, parent_scope, block_gz.break_result_loc, rhs);1712
1713 // if list grew as much as rvalue_rl_count, then a break inside operand already saved the store_to_block_ptr1713 // The loop scope has a mechanism to prevent rvalue() from emitting a
1714 const have_store_to_block = block_gz.rvalue_rl_count > block_gz.labeled_store_to_block_ptr_list.items.len;1714 // store to the result location for the loop body (since it is continues
17151715 // rather than returning a result from the loop) but here is a `break`
1716 const br = try parent_gz.addBreak(.@"break", block_inst, operand);1716 // which needs to override this behavior.
17171717 const prev_rvalue_noresult = parent_gz.rvalue_noresult;
1718 if (block_gz.break_result_loc == .block_ptr) {1718 parent_gz.rvalue_noresult = .none;
1719 try block_gz.labeled_breaks.append(astgen.gpa, br);1719 const operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_loc, rhs, node);
17201720 parent_gz.rvalue_noresult = prev_rvalue_noresult;
1721 if (have_store_to_block) {1721
1722 const zir_tags = parent_gz.astgen.instructions.items(.tag);1722 switch (block_gz.break_result_loc) {
1723 const zir_datas = parent_gz.astgen.instructions.items(.data);1723 .block_ptr => {
1724 const store_inst = @intCast(u32, zir_tags.len - 2);1724 const br = try parent_gz.addBreak(.@"break", block_inst, operand);
1725 assert(zir_tags[store_inst] == .store_to_block_ptr);1725 try block_gz.labeled_breaks.append(astgen.gpa, br);
1726 assert(zir_datas[store_inst].bin.lhs == block_gz.rl_ptr);1726
1727 try block_gz.labeled_store_to_block_ptr_list.append(astgen.gpa, store_inst);1727 // if list grew as much as rvalue_rl_count, then a break
1728 }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 },
1729 }1748 }
1730 return Zir.Inst.Ref.unreachable_value;1749 return Zir.Inst.Ref.unreachable_value;
1731 },1750 },
...@@ -4776,7 +4795,7 @@ fn arrayAccess(...@@ -4776,7 +4795,7 @@ fn arrayAccess(
4776 else => return rvalue(gz, rl, try gz.addBin(4795 else => return rvalue(gz, rl, try gz.addBin(
4777 .elem_val,4796 .elem_val,
4778 try expr(gz, scope, .none, node_datas[node].lhs),4797 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),
4780 ), node),4799 ), node),
4781 }4800 }
4782}4801}
...@@ -5183,6 +5202,7 @@ fn whileExpr(...@@ -5183,6 +5202,7 @@ fn whileExpr(
5183 // make scope now but don't stack on parent_gz until loop_scope5202 // make scope now but don't stack on parent_gz until loop_scope
5184 // gets unstacked after cont_expr is emitted and added below5203 // gets unstacked after cont_expr is emitted and added below
5185 var then_scope = parent_gz.makeSubBlock(&continue_scope.base);5204 var then_scope = parent_gz.makeSubBlock(&continue_scope.base);
5205 then_scope.markAsLoopBody(loop_scope);
5186 then_scope.instructions_top = GenZir.unstacked_top;5206 then_scope.instructions_top = GenZir.unstacked_top;
5187 defer then_scope.unstack();5207 defer then_scope.unstack();
51885208
...@@ -5267,9 +5287,6 @@ fn whileExpr(...@@ -5267,9 +5287,6 @@ fn whileExpr(
5267 then_scope.instructions_top = then_scope.instructions.items.len;5287 then_scope.instructions_top = then_scope.instructions.items.len;
5268 if (payload_inst != 0) try then_scope.instructions.append(astgen.gpa, payload_inst);5288 if (payload_inst != 0) try then_scope.instructions.append(astgen.gpa, payload_inst);
5269 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);5289 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 }
5273 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);5290 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
52745291
5275 var else_scope = parent_gz.makeSubBlock(&continue_scope.base);5292 var else_scope = parent_gz.makeSubBlock(&continue_scope.base);
...@@ -5426,6 +5443,7 @@ fn forExpr(...@@ -5426,6 +5443,7 @@ fn forExpr(
5426 }5443 }
54275444
5428 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);5445 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);
5446 then_scope.markAsLoopBody(loop_scope);
5429 defer then_scope.unstack();5447 defer then_scope.unstack();
54305448
5431 var payload_val_scope: Scope.LocalVal = undefined;5449 var payload_val_scope: Scope.LocalVal = undefined;
...@@ -5482,9 +5500,6 @@ fn forExpr(...@@ -5482,9 +5500,6 @@ fn forExpr(
5482 };5500 };
54835501
5484 const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);5502 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 }
5488 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);5503 try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
54895504
5490 var else_scope = parent_gz.makeSubBlock(&cond_scope.base);5505 var else_scope = parent_gz.makeSubBlock(&cond_scope.base);
...@@ -8369,19 +8384,25 @@ fn rvalue(...@@ -8369,19 +8384,25 @@ fn rvalue(
8369 }8384 }
8370 },8385 },
8371 .ptr => |ptr_inst| {8386 .ptr => |ptr_inst| {
8372 _ = try gz.addPlNode(.store_node, src_node, Zir.Inst.Bin{8387 if (gz.rvalue_noresult != ptr_inst) {
8373 .lhs = ptr_inst,8388 _ = try gz.addPlNode(.store_node, src_node, Zir.Inst.Bin{
8374 .rhs = result,8389 .lhs = ptr_inst,
8375 });8390 .rhs = result,
8391 });
8392 }
8376 return result;8393 return result;
8377 },8394 },
8378 .inferred_ptr => |alloc| {8395 .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 }
8380 return result;8399 return result;
8381 },8400 },
8382 .block_ptr => |block_scope| {8401 .block_ptr => |block_scope| {
8383 block_scope.rvalue_rl_count += 1;8402 if (gz.rvalue_noresult != block_scope.rl_ptr) {
8384 _ = try gz.addBin(.store_to_block_ptr, block_scope.rl_ptr, result);8403 block_scope.rvalue_rl_count += 1;
8404 _ = try gz.addBin(.store_to_block_ptr, block_scope.rl_ptr, result);
8405 }
8385 return result;8406 return result;
8386 },8407 },
8387 }8408 }
...@@ -8890,6 +8911,7 @@ const GenZir = struct {...@@ -8890,6 +8911,7 @@ const GenZir = struct {
8890 rl_ptr: Zir.Inst.Ref = .none,8911 rl_ptr: Zir.Inst.Ref = .none,
8891 /// When a block has a type result location, here it is.8912 /// When a block has a type result location, here it is.
8892 rl_ty_inst: Zir.Inst.Ref = .none,8913 rl_ty_inst: Zir.Inst.Ref = .none,
8914 rvalue_noresult: Zir.Inst.Ref = .none,
8893 /// Keeps track of how many branches of a block did not actually8915 /// Keeps track of how many branches of a block did not actually
8894 /// consume the result location. astgen uses this to figure out8916 /// consume the result location. astgen uses this to figure out
8895 /// whether to rely on break instructions or writing to the result8917 /// whether to rely on break instructions or writing to the result
...@@ -10096,6 +10118,17 @@ const GenZir = struct {...@@ -10096,6 +10118,17 @@ const GenZir = struct {
10096 }10118 }
10097 }10119 }
10098 }10120 }
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 }
10099};10132};
1010010133
10101/// This can only be for short-lived references; the memory becomes invalidated10134/// This can only be for short-lived references; the memory becomes invalidated
src/Sema.zig+3-1
...@@ -12528,7 +12528,7 @@ fn elemVal(...@@ -12528,7 +12528,7 @@ fn elemVal(
12528 block: *Block,12528 block: *Block,
12529 src: LazySrcLoc,12529 src: LazySrcLoc,
12530 array: Air.Inst.Ref,12530 array: Air.Inst.Ref,
12531 elem_index: Air.Inst.Ref,12531 elem_index_uncasted: Air.Inst.Ref,
12532 elem_index_src: LazySrcLoc,12532 elem_index_src: LazySrcLoc,
12533) CompileError!Air.Inst.Ref {12533) CompileError!Air.Inst.Ref {
12534 const array_src = src; // TODO better source location12534 const array_src = src; // TODO better source location
...@@ -12538,6 +12538,8 @@ fn elemVal(...@@ -12538,6 +12538,8 @@ fn elemVal(
12538 return sema.fail(block, src, "array access of non-indexable type '{}'", .{array_ty});12538 return sema.fail(block, src, "array access of non-indexable type '{}'", .{array_ty});
12539 }12539 }
1254012540
12541 const elem_index = try sema.coerce(block, Type.usize, elem_index_uncasted, elem_index_src);
12542
12541 switch (array_ty.zigTypeTag()) {12543 switch (array_ty.zigTypeTag()) {
12542 .Pointer => switch (array_ty.ptrSize()) {12544 .Pointer => switch (array_ty.ptrSize()) {
12543 .Slice => {12545 .Slice => {
test/behavior.zig-1
...@@ -145,7 +145,6 @@ test {...@@ -145,7 +145,6 @@ test {
145 _ = @import("behavior/bugs/7027.zig");145 _ = @import("behavior/bugs/7027.zig");
146 _ = @import("behavior/bugs/7047.zig");146 _ = @import("behavior/bugs/7047.zig");
147 _ = @import("behavior/bugs/9584.zig");147 _ = @import("behavior/bugs/9584.zig");
148 _ = @import("behavior/bugs/9967.zig");
149 _ = @import("behavior/bugs/10147.zig");148 _ = @import("behavior/bugs/10147.zig");
150 _ = @import("behavior/byteswap.zig");149 _ = @import("behavior/byteswap.zig");
151 _ = @import("behavior/call_stage1.zig");150 _ = @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" {...@@ -116,3 +116,20 @@ test "for with null and T peer types and inferred result location type" {
116 try S.doTheTest(&[_]u8{ 1, 2 });116 try S.doTheTest(&[_]u8{ 1, 2 });
117 comptime try S.doTheTest(&[_]u8{ 1, 2 });117 comptime try S.doTheTest(&[_]u8{ 1, 2 });
118}118}
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 {...@@ -26,23 +26,6 @@ fn mangleString(s: []u8) void {
26 }26 }
27}27}
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
46test "for copies its payload" {29test "for copies its payload" {
47 const S = struct {30 const S = struct {
48 fn doTheTest() !void {31 fn doTheTest() !void {
test/behavior/while.zig+30
...@@ -236,3 +236,33 @@ test "while on error union with else result follow break prong" {...@@ -236,3 +236,33 @@ test "while on error union with else result follow break prong" {
236 } else |_| @as(i32, 2);236 } else |_| @as(i32, 2);
237 try expect(result == 10);237 try expect(result == 10);
238}238}
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 @@...@@ -1,36 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const expect = std.testing.expect;2const 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
34test "while error 2 break statements and an else" {4test "while error 2 break statements and an else" {
35 const S = struct {5 const S = struct {
36 fn entry(opt_t: anyerror!bool, f: bool) !void {6 fn entry(opt_t: anyerror!bool, f: bool) !void {
test/compile_errors.zig+11
...@@ -5042,6 +5042,17 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -5042,6 +5042,17 @@ pub fn addCases(ctx: *TestContext) !void {
5042 "tmp.zig:2:12: note: control flow is diverted here",5042 "tmp.zig:2:12: note: control flow is diverted here",
5043 });5043 });
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
5045 ctx.objErrStage1("chained comparison operators",5056 ctx.objErrStage1("chained comparison operators",
5046 \\export fn a(value: u32) bool {5057 \\export fn a(value: u32) bool {
5047 \\ return 1 < value < 1000;5058 \\ return 1 < value < 1000;