mirror of
https://github.com/ziglang/zig.git
synced 2025-12-12 17:23:09 +00:00
This is problematic because in practice it depends on whether the
compiler backend supports it too, as evidenced by the TODO comment about
LLVM not supporting some architectures that in fact do support tail
calls.
Instead this logic is organized strategically in src/target.zig, part of
the internal compiler source code, and the behavior tests in question
duplicate some logic for deciding whether to proceed with the test.
The proper place to expose this flag is in `@import("builtin")` - the
generated source file - so that third party compilers can advertise
whether they support tail calls.
332 lines
9.7 KiB
Zig
332 lines
9.7 KiB
Zig
const builtin = @import("builtin");
|
|
const std = @import("std");
|
|
const expect = std.testing.expect;
|
|
const expectEqual = std.testing.expectEqual;
|
|
|
|
test "super basic invocations" {
|
|
const foo = struct {
|
|
fn foo() i32 {
|
|
return 1234;
|
|
}
|
|
}.foo;
|
|
try expect(@call(.{}, foo, .{}) == 1234);
|
|
comptime try expect(@call(.{ .modifier = .always_inline }, foo, .{}) == 1234);
|
|
{
|
|
// comptime call without comptime keyword
|
|
const result = @call(.{ .modifier = .compile_time }, foo, .{}) == 1234;
|
|
comptime try expect(result);
|
|
}
|
|
}
|
|
|
|
test "basic invocations" {
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
|
|
const foo = struct {
|
|
fn foo() i32 {
|
|
return 1234;
|
|
}
|
|
}.foo;
|
|
try expect(@call(.{}, foo, .{}) == 1234);
|
|
comptime {
|
|
// modifiers that allow comptime calls
|
|
try expect(@call(.{}, foo, .{}) == 1234);
|
|
try expect(@call(.{ .modifier = .no_async }, foo, .{}) == 1234);
|
|
try expect(@call(.{ .modifier = .always_tail }, foo, .{}) == 1234);
|
|
try expect(@call(.{ .modifier = .always_inline }, foo, .{}) == 1234);
|
|
}
|
|
{
|
|
// comptime call without comptime keyword
|
|
const result = @call(.{ .modifier = .compile_time }, foo, .{}) == 1234;
|
|
comptime try expect(result);
|
|
}
|
|
{
|
|
// call of non comptime-known function
|
|
var alias_foo = switch (builtin.zig_backend) {
|
|
.stage1 => foo,
|
|
else => &foo,
|
|
};
|
|
try expect(@call(.{ .modifier = .no_async }, alias_foo, .{}) == 1234);
|
|
try expect(@call(.{ .modifier = .never_tail }, alias_foo, .{}) == 1234);
|
|
try expect(@call(.{ .modifier = .never_inline }, alias_foo, .{}) == 1234);
|
|
}
|
|
}
|
|
|
|
test "tuple parameters" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
|
|
|
const add = struct {
|
|
fn add(a: i32, b: i32) i32 {
|
|
return a + b;
|
|
}
|
|
}.add;
|
|
var a: i32 = 12;
|
|
var b: i32 = 34;
|
|
try expect(@call(.{}, add, .{ a, 34 }) == 46);
|
|
try expect(@call(.{}, add, .{ 12, b }) == 46);
|
|
try expect(@call(.{}, add, .{ a, b }) == 46);
|
|
try expect(@call(.{}, add, .{ 12, 34 }) == 46);
|
|
if (builtin.zig_backend == .stage1) comptime try expect(@call(.{}, add, .{ 12, 34 }) == 46); // TODO
|
|
try expect(comptime @call(.{}, add, .{ 12, 34 }) == 46);
|
|
{
|
|
const separate_args0 = .{ a, b };
|
|
const separate_args1 = .{ a, 34 };
|
|
const separate_args2 = .{ 12, 34 };
|
|
const separate_args3 = .{ 12, b };
|
|
try expect(@call(.{ .modifier = .always_inline }, add, separate_args0) == 46);
|
|
try expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46);
|
|
try expect(@call(.{ .modifier = .always_inline }, add, separate_args2) == 46);
|
|
try expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46);
|
|
}
|
|
}
|
|
|
|
test "result location of function call argument through runtime condition and struct init" {
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
|
|
const E = enum { a, b };
|
|
const S = struct {
|
|
e: E,
|
|
};
|
|
const namespace = struct {
|
|
fn foo(s: S) !void {
|
|
try expect(s.e == .b);
|
|
}
|
|
};
|
|
var runtime = true;
|
|
try namespace.foo(.{
|
|
.e = if (!runtime) .a else .b,
|
|
});
|
|
}
|
|
|
|
test "function call with 40 arguments" {
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
|
|
const S = struct {
|
|
fn doTheTest(thirty_nine: i32) !void {
|
|
const result = add(
|
|
0,
|
|
1,
|
|
2,
|
|
3,
|
|
4,
|
|
5,
|
|
6,
|
|
7,
|
|
8,
|
|
9,
|
|
10,
|
|
11,
|
|
12,
|
|
13,
|
|
14,
|
|
15,
|
|
16,
|
|
17,
|
|
18,
|
|
19,
|
|
20,
|
|
21,
|
|
22,
|
|
23,
|
|
24,
|
|
25,
|
|
26,
|
|
27,
|
|
28,
|
|
29,
|
|
30,
|
|
31,
|
|
32,
|
|
33,
|
|
34,
|
|
35,
|
|
36,
|
|
37,
|
|
38,
|
|
thirty_nine,
|
|
40,
|
|
);
|
|
try expect(result == 820);
|
|
try expect(thirty_nine == 39);
|
|
}
|
|
|
|
fn add(
|
|
a0: i32,
|
|
a1: i32,
|
|
a2: i32,
|
|
a3: i32,
|
|
a4: i32,
|
|
a5: i32,
|
|
a6: i32,
|
|
a7: i32,
|
|
a8: i32,
|
|
a9: i32,
|
|
a10: i32,
|
|
a11: i32,
|
|
a12: i32,
|
|
a13: i32,
|
|
a14: i32,
|
|
a15: i32,
|
|
a16: i32,
|
|
a17: i32,
|
|
a18: i32,
|
|
a19: i32,
|
|
a20: i32,
|
|
a21: i32,
|
|
a22: i32,
|
|
a23: i32,
|
|
a24: i32,
|
|
a25: i32,
|
|
a26: i32,
|
|
a27: i32,
|
|
a28: i32,
|
|
a29: i32,
|
|
a30: i32,
|
|
a31: i32,
|
|
a32: i32,
|
|
a33: i32,
|
|
a34: i32,
|
|
a35: i32,
|
|
a36: i32,
|
|
a37: i32,
|
|
a38: i32,
|
|
a39: i32,
|
|
a40: i32,
|
|
) i32 {
|
|
return a0 +
|
|
a1 +
|
|
a2 +
|
|
a3 +
|
|
a4 +
|
|
a5 +
|
|
a6 +
|
|
a7 +
|
|
a8 +
|
|
a9 +
|
|
a10 +
|
|
a11 +
|
|
a12 +
|
|
a13 +
|
|
a14 +
|
|
a15 +
|
|
a16 +
|
|
a17 +
|
|
a18 +
|
|
a19 +
|
|
a20 +
|
|
a21 +
|
|
a22 +
|
|
a23 +
|
|
a24 +
|
|
a25 +
|
|
a26 +
|
|
a27 +
|
|
a28 +
|
|
a29 +
|
|
a30 +
|
|
a31 +
|
|
a32 +
|
|
a33 +
|
|
a34 +
|
|
a35 +
|
|
a36 +
|
|
a37 +
|
|
a38 +
|
|
a39 +
|
|
a40;
|
|
}
|
|
};
|
|
try S.doTheTest(39);
|
|
}
|
|
|
|
test "arguments to comptime parameters generated in comptime blocks" {
|
|
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
|
|
|
const S = struct {
|
|
fn fortyTwo() i32 {
|
|
return 42;
|
|
}
|
|
|
|
fn foo(comptime x: i32) void {
|
|
if (x != 42) @compileError("bad");
|
|
}
|
|
};
|
|
S.foo(S.fortyTwo());
|
|
}
|
|
|
|
test "forced tail call" {
|
|
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
|
|
|
if (builtin.zig_backend == .stage2_llvm) {
|
|
// Only attempt this test on targets we know have tail call support in LLVM.
|
|
if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
|
|
return error.SkipZigTest;
|
|
}
|
|
}
|
|
|
|
const S = struct {
|
|
fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {
|
|
if (n == 0) return a;
|
|
if (n == 1) return b;
|
|
return @call(
|
|
.{ .modifier = .always_tail },
|
|
fibonacciTailInternal,
|
|
.{ n - 1, b, a + b },
|
|
);
|
|
}
|
|
|
|
fn fibonacciTail(n: u16) u16 {
|
|
return fibonacciTailInternal(n, 0, 1);
|
|
}
|
|
};
|
|
try expect(S.fibonacciTail(10) == 55);
|
|
}
|
|
|
|
test "inline call preserves tail call" {
|
|
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
|
|
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
|
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
|
|
|
if (builtin.zig_backend == .stage2_llvm) {
|
|
// Only attempt this test on targets we know have tail call support in LLVM.
|
|
if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
|
|
return error.SkipZigTest;
|
|
}
|
|
}
|
|
|
|
const max = std.math.maxInt(u16);
|
|
const S = struct {
|
|
var a: u16 = 0;
|
|
fn foo() void {
|
|
return bar();
|
|
}
|
|
|
|
inline fn bar() void {
|
|
if (a == max) return;
|
|
// Stack overflow if not tail called
|
|
var buf: [max]u16 = undefined;
|
|
buf[a] = a;
|
|
a += 1;
|
|
return @call(.{ .modifier = .always_tail }, foo, .{});
|
|
}
|
|
};
|
|
S.foo();
|
|
try expect(S.a == std.math.maxInt(u16));
|
|
}
|