| author | |
| committer | |
| log | 67a44211f7a442d33096cc0dfff059eee9315bc6 |
| tree | 7cdb35205977cab28a9ed8cc5846096d2a57ed4d |
| parent | 0a42602418dcaf08f13b4220b6c216356f87cbfc |
Closes #4301
Closes #5692
Closes #6281
Closes #10786
Closes #11149
Closes #117765 files changed, 114 insertions(+), 6 deletions(-)
src/Sema.zig+29-5| ... | ... | @@ -6152,9 +6152,23 @@ fn analyzeCall( |
| 6152 | 6152 | if (ensure_result_used) { |
| 6153 | 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 | 6158 | return result; |
| 6156 | 6159 | } |
| 6157 | 6160 | |
| 6161 | fn 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 | ||
| 6158 | 6172 | fn analyzeInlineCallArg( |
| 6159 | 6173 | sema: *Sema, |
| 6160 | 6174 | arg_block: *Block, |
| ... | ... | @@ -6670,7 +6684,8 @@ fn instantiateGenericCall( |
| 6670 | 6684 | try sema.requireFunctionBlock(block, call_src); |
| 6671 | 6685 | |
| 6672 | 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 | 6689 | const runtime_args_len = @intCast(u32, new_fn_info.param_types.len); |
| 6675 | 6690 | const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); |
| 6676 | 6691 | { |
| ... | ... | @@ -6717,7 +6732,7 @@ fn instantiateGenericCall( |
| 6717 | 6732 | |
| 6718 | 6733 | try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + |
| 6719 | 6734 | runtime_args_len); |
| 6720 | const func_inst = try block.addInst(.{ | |
| 6735 | const result = try block.addInst(.{ | |
| 6721 | 6736 | .tag = call_tag, |
| 6722 | 6737 | .data = .{ .pl_op = .{ |
| 6723 | 6738 | .operand = callee_inst, |
| ... | ... | @@ -6729,9 +6744,12 @@ fn instantiateGenericCall( |
| 6729 | 6744 | sema.appendRefsAssumeCapacity(runtime_args); |
| 6730 | 6745 | |
| 6731 | 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 | } |
| 6736 | 6754 | |
| 6737 | 6755 | fn emitDbgInline( |
| ... | ... | @@ -19262,7 +19280,7 @@ fn resolveCallOptions( |
| 19262 | 19280 | return wanted_modifier; |
| 19263 | 19281 | }, |
| 19264 | 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 | 19284 | _ = (try sema.resolveDefinedValue(block, func_src, func)) orelse { |
| 19267 | 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 | 19290 | } |
| 19273 | 19291 | return wanted_modifier; |
| 19274 | 19292 | }, |
| 19293 | .always_tail => { | |
| 19294 | if (is_comptime) { | |
| 19295 | return .compile_time; | |
| 19296 | } | |
| 19297 | return wanted_modifier; | |
| 19298 | }, | |
| 19275 | 19299 | .async_kw => { |
| 19276 | 19300 | if (is_nosuspend) { |
| 19277 | 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 | 4522 | "", |
| 4523 | 4523 | ); |
| 4524 | 4524 | |
| 4525 | if (return_type.isNoReturn()) { | |
| 4525 | if (return_type.isNoReturn() and attr != .AlwaysTail) { | |
| 4526 | 4526 | _ = self.builder.buildUnreachable(); |
| 4527 | 4527 | return null; |
| 4528 | 4528 | } |
test/behavior/call.zig+54| ... | ... | @@ -261,3 +261,57 @@ test "arguments to comptime parameters generated in comptime blocks" { |
| 261 | 261 | }; |
| 262 | 262 | S.foo(S.fortyTwo()); |
| 263 | 263 | } |
| 264 | ||
| 265 | test "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 | ||
| 291 | test "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 @@ |
| 1 | fn myFn(_: usize) void { | |
| 2 | return; | |
| 3 | } | |
| 4 | pub 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 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = std.builtin; | |
| 3 | pub fn foo(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn { | |
| 4 | @call(.{ .modifier = .always_tail }, bar, .{ message, stack_trace }); | |
| 5 | } | |
| 6 | pub fn bar(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn { | |
| 7 | _ = message; | |
| 8 | _ = stack_trace; | |
| 9 | std.process.exit(0); | |
| 10 | } | |
| 11 | ||
| 12 | pub fn main() void { | |
| 13 | foo("foo", null); | |
| 14 | } | |
| 15 | ||
| 16 | // run | |
| 17 | // backend=llvm | |
| 18 | // target=native |