authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-08-30 14:04:13+03:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-30 12:22:07-07:00
log67a44211f7a442d33096cc0dfff059eee9315bc6
tree7cdb35205977cab28a9ed8cc5846096d2a57ed4d
parent0a42602418dcaf08f13b4220b6c216356f87cbfc

Sema: improve handling of always_tail call modifier

Closes #4301 Closes #5692 Closes #6281 Closes #10786 Closes #11149 Closes #11776

5 files changed, 114 insertions(+), 6 deletions(-)

src/Sema.zig+29-5
...@@ -6152,9 +6152,23 @@ fn analyzeCall(...@@ -6152,9 +6152,23 @@ fn analyzeCall(
6152 if (ensure_result_used) {6152 if (ensure_result_used) {
6153 try sema.ensureResultUsed(block, result, call_src);6153 try sema.ensureResultUsed(block, result, call_src);
6154 }6154 }
6155 if (call_tag == .call_always_tail) {
6156 return sema.handleTailCall(block, call_src, func_ty, result);
6157 }
6155 return result;6158 return result;
6156}6159}
61576160
6161fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref {
6162 const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl);
6163 if (!func_ty.eql(func_decl.ty, sema.mod)) {
6164 return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{
6165 func_ty.fmt(sema.mod), func_decl.ty.fmt(sema.mod),
6166 });
6167 }
6168 _ = try block.addUnOp(.ret, result);
6169 return Air.Inst.Ref.unreachable_value;
6170}
6171
6158fn analyzeInlineCallArg(6172fn analyzeInlineCallArg(
6159 sema: *Sema,6173 sema: *Sema,
6160 arg_block: *Block,6174 arg_block: *Block,
...@@ -6670,7 +6684,8 @@ fn instantiateGenericCall(...@@ -6670,7 +6684,8 @@ fn instantiateGenericCall(
6670 try sema.requireFunctionBlock(block, call_src);6684 try sema.requireFunctionBlock(block, call_src);
66716685
6672 const comptime_args = callee.comptime_args.?;6686 const comptime_args = callee.comptime_args.?;
6673 const new_fn_info = mod.declPtr(callee.owner_decl).ty.fnInfo();6687 const func_ty = mod.declPtr(callee.owner_decl).ty;
6688 const new_fn_info = func_ty.fnInfo();
6674 const runtime_args_len = @intCast(u32, new_fn_info.param_types.len);6689 const runtime_args_len = @intCast(u32, new_fn_info.param_types.len);
6675 const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len);6690 const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len);
6676 {6691 {
...@@ -6717,7 +6732,7 @@ fn instantiateGenericCall(...@@ -6717,7 +6732,7 @@ fn instantiateGenericCall(
67176732
6718 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len +6733 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len +
6719 runtime_args_len);6734 runtime_args_len);
6720 const func_inst = try block.addInst(.{6735 const result = try block.addInst(.{
6721 .tag = call_tag,6736 .tag = call_tag,
6722 .data = .{ .pl_op = .{6737 .data = .{ .pl_op = .{
6723 .operand = callee_inst,6738 .operand = callee_inst,
...@@ -6729,9 +6744,12 @@ fn instantiateGenericCall(...@@ -6729,9 +6744,12 @@ fn instantiateGenericCall(
6729 sema.appendRefsAssumeCapacity(runtime_args);6744 sema.appendRefsAssumeCapacity(runtime_args);
67306745
6731 if (ensure_result_used) {6746 if (ensure_result_used) {
6732 try sema.ensureResultUsed(block, func_inst, call_src);6747 try sema.ensureResultUsed(block, result, call_src);
6733 }6748 }
6734 return func_inst;6749 if (call_tag == .call_always_tail) {
6750 return sema.handleTailCall(block, call_src, func_ty, result);
6751 }
6752 return result;
6735}6753}
67366754
6737fn emitDbgInline(6755fn emitDbgInline(
...@@ -19262,7 +19280,7 @@ fn resolveCallOptions(...@@ -19262,7 +19280,7 @@ fn resolveCallOptions(
19262 return wanted_modifier;19280 return wanted_modifier;
19263 },19281 },
19264 // These can be upgraded to comptime. nosuspend bit can be safely ignored.19282 // These can be upgraded to comptime. nosuspend bit can be safely ignored.
19265 .always_tail, .always_inline, .compile_time => {19283 .always_inline, .compile_time => {
19266 _ = (try sema.resolveDefinedValue(block, func_src, func)) orelse {19284 _ = (try sema.resolveDefinedValue(block, func_src, func)) orelse {
19267 return sema.fail(block, func_src, "modifier '{s}' requires a comptime-known function", .{@tagName(wanted_modifier)});19285 return sema.fail(block, func_src, "modifier '{s}' requires a comptime-known function", .{@tagName(wanted_modifier)});
19268 };19286 };
...@@ -19272,6 +19290,12 @@ fn resolveCallOptions(...@@ -19272,6 +19290,12 @@ fn resolveCallOptions(
19272 }19290 }
19273 return wanted_modifier;19291 return wanted_modifier;
19274 },19292 },
19293 .always_tail => {
19294 if (is_comptime) {
19295 return .compile_time;
19296 }
19297 return wanted_modifier;
19298 },
19275 .async_kw => {19299 .async_kw => {
19276 if (is_nosuspend) {19300 if (is_nosuspend) {
19277 return sema.fail(block, modifier_src, "modifier 'async_kw' cannot be used inside nosuspend block", .{});19301 return sema.fail(block, modifier_src, "modifier 'async_kw' cannot be used inside nosuspend block", .{});
src/codegen/llvm.zig+1-1
...@@ -4522,7 +4522,7 @@ pub const FuncGen = struct {...@@ -4522,7 +4522,7 @@ pub const FuncGen = struct {
4522 "",4522 "",
4523 );4523 );
45244524
4525 if (return_type.isNoReturn()) {4525 if (return_type.isNoReturn() and attr != .AlwaysTail) {
4526 _ = self.builder.buildUnreachable();4526 _ = self.builder.buildUnreachable();
4527 return null;4527 return null;
4528 }4528 }
test/behavior/call.zig+54
...@@ -261,3 +261,57 @@ test "arguments to comptime parameters generated in comptime blocks" {...@@ -261,3 +261,57 @@ test "arguments to comptime parameters generated in comptime blocks" {
261 };261 };
262 S.foo(S.fortyTwo());262 S.foo(S.fortyTwo());
263}263}
264
265test "forced tail call" {
266 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
267 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
268 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
269 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
270 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
271 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
272
273 const S = struct {
274 fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {
275 if (n == 0) return a;
276 if (n == 1) return b;
277 return @call(
278 .{ .modifier = .always_tail },
279 fibonacciTailInternal,
280 .{ n - 1, b, a + b },
281 );
282 }
283
284 fn fibonacciTail(n: u16) u16 {
285 return fibonacciTailInternal(n, 0, 1);
286 }
287 };
288 try expect(S.fibonacciTail(10) == 55);
289}
290
291test "inline call preserves tail call" {
292 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
293 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
294 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
295 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
296 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
297 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
298
299 const max = std.math.maxInt(u16);
300 const S = struct {
301 var a: u16 = 0;
302 fn foo() void {
303 return bar();
304 }
305
306 inline fn bar() void {
307 if (a == max) return;
308 // Stack overflow if not tail called
309 var buf: [max]u16 = undefined;
310 buf[a] = a;
311 a += 1;
312 return @call(.{ .modifier = .always_tail }, foo, .{});
313 }
314 };
315 S.foo();
316 try expect(S.a == std.math.maxInt(u16));
317}
test/cases/compile_errors/invalid_tail_call.zig created+12
...@@ -0,0 +1,12 @@
1fn myFn(_: usize) void {
2 return;
3}
4pub export fn entry() void {
5 @call(.{ .modifier = .always_tail }, myFn, .{0});
6}
7
8// error
9// backend=llvm
10// target=native
11//
12// :5:5: error: unable to perform tail call: type of function being called 'fn(usize) void' does not match type of calling function 'fn() callconv(.C) void'
test/cases/taill_call_noreturn.zig created+18
...@@ -0,0 +1,18 @@
1const std = @import("std");
2const builtin = std.builtin;
3pub fn foo(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn {
4 @call(.{ .modifier = .always_tail }, bar, .{ message, stack_trace });
5}
6pub fn bar(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn {
7 _ = message;
8 _ = stack_trace;
9 std.process.exit(0);
10}
11
12pub fn main() void {
13 foo("foo", null);
14}
15
16// run
17// backend=llvm
18// target=native