zig/test/behavior/call.zig
Andrew Kelley 7377dce368 avoid exposing supportsTailCall in the standard library
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.
2022-08-30 12:50:15 -07:00

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));
}