authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-06 19:40:55-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-08-06 19:53:04-07:00
log7d0de54ad44832589379a4bcbba493db2087bebf
tree5efb0a82c4ad399e25f603ba25cc6e36566ca845
parente974d4c4295c4fbdbc239caa2cf2d653f65662f1

stage2: fix return pointer result locations

* Introduce `ret_load` ZIR instruction which does return semantics based on a corresponding `ret_ptr` instruction. If the return type of the function has storage for the return type, it simply returns. However if the return type of the function is by-value, it loads the return value from the `ret_ptr` allocation and returns that. * AstGen: improve `finishThenElseBlock` to not emit break instructions after a return instruction in the same block. * Sema: `ret_ptr` instruction works correctly in comptime contexts. Same with `alloc_mut`. The test case with a recursive inline function having an implicitly comptime return value now has a runtime return value because of the fact that it calls a function in a non-comptime context.

5 files changed, 92 insertions(+), 56 deletions(-)

src/AstGen.zig+25-9
......@@ -2131,6 +2131,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner
21312131 .condbr_inline,
21322132 .compile_error,
21332133 .ret_node,
2134 .ret_load,
21342135 .ret_coerce,
21352136 .ret_err_value,
21362137 .@"unreachable",
......@@ -4791,11 +4792,10 @@ fn finishThenElseBlock(
47914792 const strat = rl.strategy(block_scope);
47924793 switch (strat.tag) {
47934794 .break_void => {
4794 if (!parent_gz.refIsNoReturn(then_result)) {
4795 if (!then_scope.endsWithNoReturn()) {
47954796 _ = try then_scope.addBreak(break_tag, then_break_block, .void_value);
47964797 }
4797 const elide_else = if (else_result != .none) parent_gz.refIsNoReturn(else_result) else false;
4798 if (!elide_else) {
4798 if (!else_scope.endsWithNoReturn()) {
47994799 _ = try else_scope.addBreak(break_tag, main_block, .void_value);
48004800 }
48014801 assert(!strat.elide_store_to_block_ptr_instructions);
......@@ -4803,11 +4803,11 @@ fn finishThenElseBlock(
48034803 return indexToRef(main_block);
48044804 },
48054805 .break_operand => {
4806 if (!parent_gz.refIsNoReturn(then_result)) {
4806 if (!then_scope.endsWithNoReturn()) {
48074807 _ = try then_scope.addBreak(break_tag, then_break_block, then_result);
48084808 }
48094809 if (else_result != .none) {
4810 if (!parent_gz.refIsNoReturn(else_result)) {
4810 if (!else_scope.endsWithNoReturn()) {
48114811 _ = try else_scope.addBreak(break_tag, main_block, else_result);
48124812 }
48134813 } else {
......@@ -6236,7 +6236,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
62366236 // Value is always an error. Emit both error defers and regular defers.
62376237 const err_code = try gz.addUnNode(.err_union_code, operand, node);
62386238 try genDefers(gz, defer_outer, scope, .{ .both = err_code });
6239 _ = try gz.addUnNode(.ret_node, operand, node);
6239 try gz.addRet(rl, operand, node);
62406240 return Zir.Inst.Ref.unreachable_value;
62416241 },
62426242 .maybe => {
......@@ -6244,7 +6244,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
62446244 if (!defer_counts.have_err) {
62456245 // Only regular defers; no branch needed.
62466246 try genDefers(gz, defer_outer, scope, .normal_only);
6247 _ = try gz.addUnNode(.ret_node, operand, node);
6247 try gz.addRet(rl, operand, node);
62486248 return Zir.Inst.Ref.unreachable_value;
62496249 }
62506250
......@@ -6256,7 +6256,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
62566256 defer then_scope.instructions.deinit(astgen.gpa);
62576257
62586258 try genDefers(&then_scope, defer_outer, scope, .normal_only);
6259 _ = try then_scope.addUnNode(.ret_node, operand, node);
6259 try then_scope.addRet(rl, operand, node);
62606260
62616261 var else_scope = gz.makeSubBlock(scope);
62626262 defer else_scope.instructions.deinit(astgen.gpa);
......@@ -6265,7 +6265,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref
62656265 .both = try else_scope.addUnNode(.err_union_code, operand, node),
62666266 };
62676267 try genDefers(&else_scope, defer_outer, scope, which_ones);
6268 _ = try else_scope.addUnNode(.ret_node, operand, node);
6268 try else_scope.addRet(rl, operand, node);
62696269
62706270 try setCondBrPayload(condbr, is_non_err, &then_scope, &else_scope);
62716271
......@@ -9003,6 +9003,14 @@ const GenZir = struct {
90039003 used: bool = false,
90049004 };
90059005
9006 fn endsWithNoReturn(gz: GenZir) bool {
9007 const tags = gz.astgen.instructions.items(.tag);
9008 if (gz.instructions.items.len == 0) return false;
9009 const last_inst = gz.instructions.items[gz.instructions.items.len - 1];
9010 return tags[last_inst].isNoReturn();
9011 }
9012
9013 /// TODO all uses of this should be replaced with uses of `endsWithNoReturn`.
90069014 fn refIsNoReturn(gz: GenZir, inst_ref: Zir.Inst.Ref) bool {
90079015 if (inst_ref == .unreachable_value) return true;
90089016 if (refToIndex(inst_ref)) |inst_index| {
......@@ -9977,6 +9985,14 @@ const GenZir = struct {
99779985 gz.instructions.appendAssumeCapacity(new_index);
99789986 return new_index;
99799987 }
9988
9989 fn addRet(gz: *GenZir, rl: ResultLoc, operand: Zir.Inst.Ref, node: ast.Node.Index) !void {
9990 switch (rl) {
9991 .ptr => |ret_ptr| _ = try gz.addUnNode(.ret_load, ret_ptr, node),
9992 .ty => _ = try gz.addUnNode(.ret_node, operand, node),
9993 else => unreachable,
9994 }
9995 }
99809996};
99819997
99829998/// This can only be for short-lived references; the memory becomes invalidated
src/Sema.zig+49-17
......@@ -366,6 +366,7 @@ pub fn analyzeBody(
366366 .compile_error => return sema.zirCompileError(block, inst),
367367 .ret_coerce => return sema.zirRetCoerce(block, inst),
368368 .ret_node => return sema.zirRetNode(block, inst),
369 .ret_load => return sema.zirRetLoad(block, inst),
369370 .ret_err_value => return sema.zirRetErrValue(block, inst),
370371 .@"unreachable" => return sema.zirUnreachable(block, inst),
371372 .repeat => return sema.zirRepeat(block, inst),
......@@ -718,8 +719,8 @@ fn resolveMaybeUndefValAllowVariables(
718719 if (try sema.typeHasOnePossibleValue(block, src, sema.typeOf(inst))) |opv| {
719720 return opv;
720721 }
721
722 switch (sema.air_instructions.items(.tag)[i]) {
722 const air_tags = sema.air_instructions.items(.tag);
723 switch (air_tags[i]) {
723724 .constant => {
724725 const ty_pl = sema.air_instructions.items(.data)[i].ty_pl;
725726 return sema.air_values.items[ty_pl.payload];
......@@ -1248,6 +1249,11 @@ fn zirRetPtr(
12481249
12491250 const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };
12501251 try sema.requireFunctionBlock(block, src);
1252
1253 if (block.is_comptime) {
1254 return sema.analyzeComptimeAlloc(block, sema.fn_ret_ty);
1255 }
1256
12511257 const ptr_type = try Module.simplePtrType(sema.arena, sema.fn_ret_ty, true, .One);
12521258 return block.addTy(.alloc, ptr_type);
12531259}
......@@ -1375,21 +1381,7 @@ fn zirAllocComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp
13751381 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
13761382 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
13771383 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
1378 const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One);
1379
1380 var anon_decl = try block.startAnonDecl();
1381 defer anon_decl.deinit();
1382 const decl = try anon_decl.finish(
1383 try var_type.copy(anon_decl.arena()),
1384 // AstGen guarantees there will be a store before the first load, so we put a value
1385 // here indicating there is no valid value.
1386 Value.initTag(.unreachable_value),
1387 );
1388 try sema.mod.declareDeclDependency(sema.owner_decl, decl);
1389 return sema.addConstant(ptr_type, try Value.Tag.decl_ref_mut.create(sema.arena, .{
1390 .runtime_index = block.runtime_index,
1391 .decl = decl,
1392 }));
1384 return sema.analyzeComptimeAlloc(block, var_type);
13931385}
13941386
13951387fn zirAllocInferredComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -1419,6 +1411,9 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
14191411 const var_decl_src = inst_data.src();
14201412 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
14211413 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
1414 if (block.is_comptime) {
1415 return sema.analyzeComptimeAlloc(block, var_type);
1416 }
14221417 try sema.validateVarType(block, ty_src, var_type);
14231418 const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One);
14241419 try sema.requireRuntimeBlock(block, var_decl_src);
......@@ -6280,6 +6275,21 @@ fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
62806275 return sema.analyzeRet(block, operand, src, false);
62816276}
62826277
6278fn zirRetLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
6279 const tracy = trace(@src());
6280 defer tracy.end();
6281
6282 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
6283 const src = inst_data.src();
6284 // TODO: when implementing functions that accept a result location pointer,
6285 // this logic will be updated to only do a load in case that the function's return
6286 // type in fact does not need a result location pointer. Until then we assume
6287 // the `ret_ptr` is the same as an `alloc` and do a load here.
6288 const ret_ptr = sema.resolveInst(inst_data.operand);
6289 const operand = try sema.analyzeLoad(block, src, ret_ptr, src);
6290 return sema.analyzeRet(block, operand, src, false);
6291}
6292
62836293fn analyzeRet(
62846294 sema: *Sema,
62856295 block: *Scope.Block,
......@@ -9416,3 +9426,25 @@ fn isComptimeKnown(
94169426) !bool {
94179427 return (try sema.resolveMaybeUndefVal(block, src, inst)) != null;
94189428}
9429
9430fn analyzeComptimeAlloc(
9431 sema: *Sema,
9432 block: *Scope.Block,
9433 var_type: Type,
9434) CompileError!Air.Inst.Ref {
9435 const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One);
9436
9437 var anon_decl = try block.startAnonDecl();
9438 defer anon_decl.deinit();
9439 const decl = try anon_decl.finish(
9440 try var_type.copy(anon_decl.arena()),
9441 // AstGen guarantees there will be a store before the first load, so we put a value
9442 // here indicating there is no valid value.
9443 Value.initTag(.unreachable_value),
9444 );
9445 try sema.mod.declareDeclDependency(sema.owner_decl, decl);
9446 return sema.addConstant(ptr_type, try Value.Tag.decl_ref_mut.create(sema.arena, .{
9447 .runtime_index = block.runtime_index,
9448 .decl = decl,
9449 }));
9450}
src/Zir.zig+8
......@@ -465,6 +465,11 @@ pub const Inst = struct {
465465 /// Uses the `un_node` union field.
466466 ret_node,
467467 /// Sends control flow back to the function's callee.
468 /// The operand is a `ret_ptr` instruction, where the return value can be found.
469 /// Includes an AST node source location.
470 /// Uses the `un_node` union field.
471 ret_load,
472 /// Sends control flow back to the function's callee.
468473 /// Includes an operand as the return value.
469474 /// Includes a token source location.
470475 /// Uses the `un_tok` union field.
......@@ -1231,6 +1236,7 @@ pub const Inst = struct {
12311236 .condbr_inline,
12321237 .compile_error,
12331238 .ret_node,
1239 .ret_load,
12341240 .ret_coerce,
12351241 .ret_err_value,
12361242 .@"unreachable",
......@@ -1335,6 +1341,7 @@ pub const Inst = struct {
13351341 .param_type = .param_type,
13361342 .ref = .un_tok,
13371343 .ret_node = .un_node,
1344 .ret_load = .un_node,
13381345 .ret_coerce = .un_tok,
13391346 .ret_err_value = .str_tok,
13401347 .ret_err_value_code = .str_tok,
......@@ -2912,6 +2919,7 @@ const Writer = struct {
29122919 .ensure_result_used,
29132920 .ensure_result_non_error,
29142921 .ret_node,
2922 .ret_load,
29152923 .resolve_inferred_alloc,
29162924 .optional_type,
29172925 .optional_payload_safe,
test/behavior/generics.zig+5-29
......@@ -28,16 +28,7 @@ test "simple generic fn" {
2828}
2929
3030fn max(comptime T: type, a: T, b: T) T {
31 if (!builtin.zig_is_stage2) {
32 // TODO: stage2 is incorrectly emitting AIR that allocates a result
33 // value, stores to it, but then returns void instead of the result.
34 return if (a > b) a else b;
35 }
36 if (a > b) {
37 return a;
38 } else {
39 return b;
40 }
31 return if (a > b) a else b;
4132}
4233
4334fn add(comptime a: i32, b: i32) i32 {
......@@ -70,29 +61,14 @@ test "fn with comptime args" {
7061test "anytype params" {
7162 try expect(max_i32(12, 34) == 34);
7263 try expect(max_f64(1.2, 3.4) == 3.4);
73 if (!builtin.zig_is_stage2) {
74 // TODO: stage2 is incorrectly hitting the following problem:
75 // error: unable to resolve comptime value
76 // return max_anytype(a, b);
77 // ^
78 comptime {
79 try expect(max_i32(12, 34) == 34);
80 try expect(max_f64(1.2, 3.4) == 3.4);
81 }
64 comptime {
65 try expect(max_i32(12, 34) == 34);
66 try expect(max_f64(1.2, 3.4) == 3.4);
8267 }
8368}
8469
8570fn max_anytype(a: anytype, b: anytype) @TypeOf(a, b) {
86 if (!builtin.zig_is_stage2) {
87 // TODO: stage2 is incorrectly emitting AIR that allocates a result
88 // value, stores to it, but then returns void instead of the result.
89 return if (a > b) a else b;
90 }
91 if (a > b) {
92 return a;
93 } else {
94 return b;
95 }
71 return if (a > b) a else b;
9672}
9773
9874fn max_i32(a: i32, b: i32) i32 {
test/stage2/cbe.zig+5-1
......@@ -240,6 +240,10 @@ pub fn addCases(ctx: *TestContext) !void {
240240 if (host_supports_custom_stack_size) {
241241 var case = ctx.exeFromCompiledC("@setEvalBranchQuota", .{});
242242
243 // TODO when adding result location support to function calls, revisit this test
244 // case. It can go back to what it was before, with `y` being comptime known.
245 // Because the ret_ptr will passed in with the inline fn call, and there will
246 // only be 1 store to it, and it will be comptime known.
243247 case.addCompareOutput(
244248 \\pub export fn main() i32 {
245249 \\ @setEvalBranchQuota(1001);
......@@ -247,7 +251,7 @@ pub fn addCases(ctx: *TestContext) !void {
247251 \\ return y - 1;
248252 \\}
249253 \\
250 \\fn rec(n: usize) callconv(.Inline) usize {
254 \\inline fn rec(n: i32) i32 {
251255 \\ if (n <= 1) return n;
252256 \\ return rec(n - 1);
253257 \\}