zig/test/stage2/cbe.zig

1048 lines
32 KiB
Zig
Raw Normal View History

const std = @import("std");
2020-09-22 05:14:01 +01:00
const TestContext = @import("../../src/test.zig").TestContext;
// These tests should work with all platforms, but we're using linux_x64 for
// now for consistency. Will be expanded eventually.
const linux_x64 = std.zig.CrossTarget{
.cpu_arch = .x86_64,
.os_tag = .linux,
};
pub fn addCases(ctx: *TestContext) !void {
{
var case = ctx.exeFromCompiledC("hello world with updates", .{});
// Regular old hello world
case.addCompareOutput(
\\extern fn puts(s: [*:0]const u8) c_int;
\\pub export fn main() c_int {
\\ _ = puts("hello world!");
\\ return 0;
\\}
, "hello world!" ++ std.cstr.line_sep);
// Now change the message only
case.addCompareOutput(
\\extern fn puts(s: [*:0]const u8) c_int;
\\pub export fn main() c_int {
\\ _ = puts("yo");
\\ return 0;
\\}
, "yo" ++ std.cstr.line_sep);
// Add an unused Decl
case.addCompareOutput(
\\extern fn puts(s: [*:0]const u8) c_int;
\\pub export fn main() c_int {
\\ _ = puts("yo!");
\\ return 0;
\\}
\\fn unused() void {}
, "yo!" ++ std.cstr.line_sep);
// Comptime return type and calling convention expected.
case.addError(
\\var x: i32 = 1234;
\\pub export fn main() x {
\\ return 0;
\\}
\\export fn foo() callconv(y) c_int {
\\ return 0;
\\}
stage2: garbage collect unused anon decls After this change, the frontend and backend cooperate to keep track of which Decls are actually emitted into the machine code. When any backend sees a `decl_ref` Value, it must mark the corresponding Decl `alive` field to true. This prevents unused comptime data from spilling into the output object files. For example, if you do an `inline for` loop, previously, any intermediate value calculations would have gone into the object file. Now they are garbage collected immediately after the owner Decl has its machine code generated. In the frontend, when it is time to send a Decl to the linker, if it has not been marked "alive" then it is deleted instead. Additional improvements: * Resolve type ABI layouts after successful semantic analysis of a Decl. This is needed so that the backend has access to struct fields. * Sema: fix incorrect logic in resolveMaybeUndefVal. It should return "not comptime known" instead of a compile error for global variables. * `Value.pointerDeref` now returns `null` in the case that the pointer deref cannot happen at compile-time. This is true for global variables, for example. Another example is if a comptime known pointer has a hard coded address value. * Binary arithmetic sets the requireRuntimeBlock source location to the lhs_src or rhs_src as appropriate instead of on the operator node. * Fix LLVM codegen for slice_elem_val which had the wrong logic for when the operand was not a pointer. As noted in the comment in the implementation of deleteUnusedDecl, a future improvement will be to rework the frontend/linker interface to remove the frontend's responsibility of calling allocateDeclIndexes. I discovered some issues with the plan9 linker backend that are related to this, and worked around them for now.
2021-07-30 03:30:37 +01:00
\\var y: @import("std").builtin.CallingConvention = .C;
, &.{
2021-05-08 02:52:11 +01:00
":2:22: error: unable to resolve comptime value",
":5:26: error: unable to resolve comptime value",
});
}
2021-01-29 10:19:10 +00:00
{
var case = ctx.exeFromCompiledC("var args", .{});
case.addCompareOutput(
\\extern fn printf(format: [*:0]const u8, ...) c_int;
\\
\\pub export fn main() c_int {
2021-01-29 10:19:10 +00:00
\\ _ = printf("Hello, %s!\n", "world");
\\ return 0;
\\}
, "Hello, world!" ++ std.cstr.line_sep);
2021-01-29 10:19:10 +00:00
}
{
var case = ctx.exeFromCompiledC("@intToError", .{});
case.addCompareOutput(
\\pub export fn main() c_int {
\\ // comptime checks
\\ const a = error.A;
\\ const b = error.B;
\\ const c = @intToError(2);
\\ const d = @intToError(1);
\\ if (!(c == b)) unreachable;
\\ if (!(a == d)) unreachable;
\\ // runtime checks
\\ var x = error.A;
\\ var y = error.B;
\\ var z = @intToError(2);
\\ var f = @intToError(1);
\\ if (!(y == z)) unreachable;
\\ if (!(x == f)) unreachable;
\\ return 0;
\\}
, "");
case.addError(
\\pub export fn main() c_int {
2021-06-12 19:48:13 +01:00
\\ _ = @intToError(0);
\\ return 0;
\\}
2021-06-12 19:48:13 +01:00
, &.{":2:21: error: integer value 0 represents no error"});
case.addError(
\\pub export fn main() c_int {
2021-06-12 19:48:13 +01:00
\\ _ = @intToError(3);
\\ return 0;
\\}
2021-06-12 19:48:13 +01:00
, &.{":2:21: error: integer value 3 represents no error"});
}
{
var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);
// Exit with 0
case.addCompareOutput(
\\fn exitGood() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ );
\\ unreachable;
\\}
\\
\\pub export fn main() c_int {
\\ exitGood();
\\}
, "");
// Pass a usize parameter to exit
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exit(0);
\\}
\\
\\fn exit(code: usize) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
, "");
// Change the parameter to u8
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exit(0);
\\}
\\
\\fn exit(code: u8) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
, "");
// Do some arithmetic at the exit callsite
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exitMath(1);
\\}
\\
\\fn exitMath(a: u8) noreturn {
\\ exit(0 + a - a);
\\}
\\
\\fn exit(code: u8) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
\\
, "");
// Invert the arithmetic
case.addCompareOutput(
\\pub export fn main() c_int {
\\ exitMath(1);
\\}
\\
\\fn exitMath(a: u8) noreturn {
\\ exit(a + 0 - a);
\\}
\\
\\fn exit(code: u8) noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (code)
\\ );
\\ unreachable;
\\}
\\
, "");
}
{
var case = ctx.exeFromCompiledC("alloc and retptr", .{});
case.addCompareOutput(
\\fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
\\
\\fn addIndirect(a: i32, b: i32) i32 {
\\ return add(a, b);
\\}
\\
\\pub export fn main() c_int {
\\ return addIndirect(1, 2) - 3;
\\}
, "");
}
stage2: inferred local variables This patch introduces the following new things: Types: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support most of the normal type queries. However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. - The payload for this type simply points to the corresponding Value payload. Values: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support any of the normal value queries. ZIR instructions: - store_to_inferred_ptr, - Same as `store` but the type of the value being stored will be used to infer the pointer type. - resolve_inferred_alloc - Each `store_to_inferred_ptr` puts the type of the stored value into a set, and then `resolve_inferred_alloc` triggers peer type resolution on the set. The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which is the allocation that needs to have its type inferred. Changes to the C backend: * Implements the bitcast instruction. If the source and dest types are both pointers, uses a cast, otherwise uses memcpy. * Tests are run with -Wno-declaration-after-statement. Someday we can conform to this but not today. In ZIR form it looks like this: ```zir fn_body main { // unanalyzed %0 = dbg_stmt() =>%1 = alloc_inferred() %2 = declval_in_module(Decl(add)) %3 = deref(%2) %4 = param_type(%3, 0) %5 = const(TypedValue{ .ty = comptime_int, .val = 1}) %6 = as(%4, %5) %7 = param_type(%3, 1) %8 = const(TypedValue{ .ty = comptime_int, .val = 2}) %9 = as(%7, %8) %10 = call(%3, [%6, %9], modifier=auto) =>%11 = store_to_inferred_ptr(%1, %10) =>%12 = resolve_inferred_alloc(%1) %13 = dbg_stmt() %14 = ret_type() %15 = const(TypedValue{ .ty = comptime_int, .val = 3}) %16 = sub(%10, %15) %17 = as(%14, %16) %18 = return(%17) } // fn_body main ``` I have not played around with very many test cases yet. Some interesting ones that I want to look at before merging: ```zig var x = blk: { var y = foo(); y.a = 1; break :blk y; }; ``` In the above test case, x and y are supposed to alias. ```zig var x = if (bar()) blk: { var y = foo(); y.a = 1; break :blk y; } else blk: { var z = baz(); z.b = 1; break :blk z; }; ``` In the above test case, x, y, and z are supposed to alias. I also haven't tested with `var` instead of `const` yet.
2020-12-31 08:54:02 +00:00
{
2020-12-31 09:42:48 +00:00
var case = ctx.exeFromCompiledC("inferred local const and var", .{});
stage2: inferred local variables This patch introduces the following new things: Types: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support most of the normal type queries. However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. - The payload for this type simply points to the corresponding Value payload. Values: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support any of the normal value queries. ZIR instructions: - store_to_inferred_ptr, - Same as `store` but the type of the value being stored will be used to infer the pointer type. - resolve_inferred_alloc - Each `store_to_inferred_ptr` puts the type of the stored value into a set, and then `resolve_inferred_alloc` triggers peer type resolution on the set. The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which is the allocation that needs to have its type inferred. Changes to the C backend: * Implements the bitcast instruction. If the source and dest types are both pointers, uses a cast, otherwise uses memcpy. * Tests are run with -Wno-declaration-after-statement. Someday we can conform to this but not today. In ZIR form it looks like this: ```zir fn_body main { // unanalyzed %0 = dbg_stmt() =>%1 = alloc_inferred() %2 = declval_in_module(Decl(add)) %3 = deref(%2) %4 = param_type(%3, 0) %5 = const(TypedValue{ .ty = comptime_int, .val = 1}) %6 = as(%4, %5) %7 = param_type(%3, 1) %8 = const(TypedValue{ .ty = comptime_int, .val = 2}) %9 = as(%7, %8) %10 = call(%3, [%6, %9], modifier=auto) =>%11 = store_to_inferred_ptr(%1, %10) =>%12 = resolve_inferred_alloc(%1) %13 = dbg_stmt() %14 = ret_type() %15 = const(TypedValue{ .ty = comptime_int, .val = 3}) %16 = sub(%10, %15) %17 = as(%14, %16) %18 = return(%17) } // fn_body main ``` I have not played around with very many test cases yet. Some interesting ones that I want to look at before merging: ```zig var x = blk: { var y = foo(); y.a = 1; break :blk y; }; ``` In the above test case, x and y are supposed to alias. ```zig var x = if (bar()) blk: { var y = foo(); y.a = 1; break :blk y; } else blk: { var z = baz(); z.b = 1; break :blk z; }; ``` In the above test case, x, y, and z are supposed to alias. I also haven't tested with `var` instead of `const` yet.
2020-12-31 08:54:02 +00:00
case.addCompareOutput(
\\fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
\\
\\pub export fn main() c_int {
stage2: inferred local variables This patch introduces the following new things: Types: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support most of the normal type queries. However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. - The payload for this type simply points to the corresponding Value payload. Values: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support any of the normal value queries. ZIR instructions: - store_to_inferred_ptr, - Same as `store` but the type of the value being stored will be used to infer the pointer type. - resolve_inferred_alloc - Each `store_to_inferred_ptr` puts the type of the stored value into a set, and then `resolve_inferred_alloc` triggers peer type resolution on the set. The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which is the allocation that needs to have its type inferred. Changes to the C backend: * Implements the bitcast instruction. If the source and dest types are both pointers, uses a cast, otherwise uses memcpy. * Tests are run with -Wno-declaration-after-statement. Someday we can conform to this but not today. In ZIR form it looks like this: ```zir fn_body main { // unanalyzed %0 = dbg_stmt() =>%1 = alloc_inferred() %2 = declval_in_module(Decl(add)) %3 = deref(%2) %4 = param_type(%3, 0) %5 = const(TypedValue{ .ty = comptime_int, .val = 1}) %6 = as(%4, %5) %7 = param_type(%3, 1) %8 = const(TypedValue{ .ty = comptime_int, .val = 2}) %9 = as(%7, %8) %10 = call(%3, [%6, %9], modifier=auto) =>%11 = store_to_inferred_ptr(%1, %10) =>%12 = resolve_inferred_alloc(%1) %13 = dbg_stmt() %14 = ret_type() %15 = const(TypedValue{ .ty = comptime_int, .val = 3}) %16 = sub(%10, %15) %17 = as(%14, %16) %18 = return(%17) } // fn_body main ``` I have not played around with very many test cases yet. Some interesting ones that I want to look at before merging: ```zig var x = blk: { var y = foo(); y.a = 1; break :blk y; }; ``` In the above test case, x and y are supposed to alias. ```zig var x = if (bar()) blk: { var y = foo(); y.a = 1; break :blk y; } else blk: { var z = baz(); z.b = 1; break :blk z; }; ``` In the above test case, x, y, and z are supposed to alias. I also haven't tested with `var` instead of `const` yet.
2020-12-31 08:54:02 +00:00
\\ const x = add(1, 2);
2020-12-31 09:42:48 +00:00
\\ var y = add(3, 0);
\\ y -= x;
\\ return y;
stage2: inferred local variables This patch introduces the following new things: Types: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support most of the normal type queries. However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. - The payload for this type simply points to the corresponding Value payload. Values: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support any of the normal value queries. ZIR instructions: - store_to_inferred_ptr, - Same as `store` but the type of the value being stored will be used to infer the pointer type. - resolve_inferred_alloc - Each `store_to_inferred_ptr` puts the type of the stored value into a set, and then `resolve_inferred_alloc` triggers peer type resolution on the set. The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which is the allocation that needs to have its type inferred. Changes to the C backend: * Implements the bitcast instruction. If the source and dest types are both pointers, uses a cast, otherwise uses memcpy. * Tests are run with -Wno-declaration-after-statement. Someday we can conform to this but not today. In ZIR form it looks like this: ```zir fn_body main { // unanalyzed %0 = dbg_stmt() =>%1 = alloc_inferred() %2 = declval_in_module(Decl(add)) %3 = deref(%2) %4 = param_type(%3, 0) %5 = const(TypedValue{ .ty = comptime_int, .val = 1}) %6 = as(%4, %5) %7 = param_type(%3, 1) %8 = const(TypedValue{ .ty = comptime_int, .val = 2}) %9 = as(%7, %8) %10 = call(%3, [%6, %9], modifier=auto) =>%11 = store_to_inferred_ptr(%1, %10) =>%12 = resolve_inferred_alloc(%1) %13 = dbg_stmt() %14 = ret_type() %15 = const(TypedValue{ .ty = comptime_int, .val = 3}) %16 = sub(%10, %15) %17 = as(%14, %16) %18 = return(%17) } // fn_body main ``` I have not played around with very many test cases yet. Some interesting ones that I want to look at before merging: ```zig var x = blk: { var y = foo(); y.a = 1; break :blk y; }; ``` In the above test case, x and y are supposed to alias. ```zig var x = if (bar()) blk: { var y = foo(); y.a = 1; break :blk y; } else blk: { var z = baz(); z.b = 1; break :blk z; }; ``` In the above test case, x, y, and z are supposed to alias. I also haven't tested with `var` instead of `const` yet.
2020-12-31 08:54:02 +00:00
\\}
, "");
}
// This will make a pretty deep call stack, so this test can only be enabled
// on hosts where Zig's linking strategy can honor the 16 MiB (default) we
// link the self-hosted compiler with.
const host_supports_custom_stack_size = @import("builtin").target.os.tag == .linux;
if (host_supports_custom_stack_size) {
var case = ctx.exeFromCompiledC("@setEvalBranchQuota", .{});
stage2: inferred local variables This patch introduces the following new things: Types: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support most of the normal type queries. However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. - The payload for this type simply points to the corresponding Value payload. Values: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support any of the normal value queries. ZIR instructions: - store_to_inferred_ptr, - Same as `store` but the type of the value being stored will be used to infer the pointer type. - resolve_inferred_alloc - Each `store_to_inferred_ptr` puts the type of the stored value into a set, and then `resolve_inferred_alloc` triggers peer type resolution on the set. The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which is the allocation that needs to have its type inferred. Changes to the C backend: * Implements the bitcast instruction. If the source and dest types are both pointers, uses a cast, otherwise uses memcpy. * Tests are run with -Wno-declaration-after-statement. Someday we can conform to this but not today. In ZIR form it looks like this: ```zir fn_body main { // unanalyzed %0 = dbg_stmt() =>%1 = alloc_inferred() %2 = declval_in_module(Decl(add)) %3 = deref(%2) %4 = param_type(%3, 0) %5 = const(TypedValue{ .ty = comptime_int, .val = 1}) %6 = as(%4, %5) %7 = param_type(%3, 1) %8 = const(TypedValue{ .ty = comptime_int, .val = 2}) %9 = as(%7, %8) %10 = call(%3, [%6, %9], modifier=auto) =>%11 = store_to_inferred_ptr(%1, %10) =>%12 = resolve_inferred_alloc(%1) %13 = dbg_stmt() %14 = ret_type() %15 = const(TypedValue{ .ty = comptime_int, .val = 3}) %16 = sub(%10, %15) %17 = as(%14, %16) %18 = return(%17) } // fn_body main ``` I have not played around with very many test cases yet. Some interesting ones that I want to look at before merging: ```zig var x = blk: { var y = foo(); y.a = 1; break :blk y; }; ``` In the above test case, x and y are supposed to alias. ```zig var x = if (bar()) blk: { var y = foo(); y.a = 1; break :blk y; } else blk: { var z = baz(); z.b = 1; break :blk z; }; ``` In the above test case, x, y, and z are supposed to alias. I also haven't tested with `var` instead of `const` yet.
2020-12-31 08:54:02 +00:00
// TODO when adding result location support to function calls, revisit this test
// case. It can go back to what it was before, with `y` being comptime known.
// Because the ret_ptr will passed in with the inline fn call, and there will
// only be 1 store to it, and it will be comptime known.
case.addCompareOutput(
\\pub export fn main() i32 {
\\ @setEvalBranchQuota(1001);
\\ const y = rec(1001);
\\ return y - 1;
\\}
\\
\\inline fn rec(n: i32) i32 {
\\ if (n <= 1) return n;
\\ return rec(n - 1);
\\}
, "");
}
2021-01-27 09:40:34 +00:00
{
var case = ctx.exeFromCompiledC("control flow", .{});
// Simple while loop
case.addCompareOutput(
\\pub export fn main() c_int {
2021-01-27 09:40:34 +00:00
\\ var a: c_int = 0;
\\ while (a < 5) : (a+=1) {}
\\ return a - 5;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var a = true;
\\ while (!a) {}
\\ return 0;
\\}
, "");
2021-01-27 09:40:34 +00:00
// If expression
case.addCompareOutput(
\\pub export fn main() c_int {
2021-01-27 09:40:34 +00:00
\\ var cond: c_int = 0;
\\ var a: c_int = @as(c_int, if (cond == 0)
\\ 2
\\ else
\\ 3) + 9;
\\ return a - 11;
\\}
, "");
// If expression with breakpoint that does not get hit
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var x: i32 = 1;
\\ if (x != 1) @breakpoint();
\\ return 0;
\\}
, "");
2021-01-27 09:40:34 +00:00
// Switch expression
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ var a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Switch expression missing else case.
case.addError(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 3 => 3,
\\ 4 => 4,
\\ };
\\ return a - 4;
\\}
, &.{":3:22: error: switch must handle all possibilities"});
// Switch expression, has an unreachable prong.
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ 13 => unreachable,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Switch expression, has an unreachable prong and prongs write
// to result locations.
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ var a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ 13 => unreachable,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Integer switch expression has duplicate case value.
case.addError(
\\pub export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 96, 11...13, 97 => 3,
\\ 0 => 4,
\\ 90, 12 => 100,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, &.{
":8:13: error: duplicate switch value",
":6:15: note: previous value here",
});
// Boolean switch expression has duplicate case value.
case.addError(
\\pub export fn main() c_int {
\\ var a: bool = false;
\\ const b: c_int = switch (a) {
\\ false => 1,
\\ true => 2,
\\ false => 3,
\\ };
2021-06-12 19:48:13 +01:00
\\ _ = b;
\\}
, &.{
":6:9: error: duplicate switch value",
});
// Sparse (no range capable) switch expression has duplicate case value.
case.addError(
\\pub export fn main() c_int {
\\ const A: type = i32;
\\ const b: c_int = switch (A) {
\\ i32 => 1,
\\ bool => 2,
\\ f64, i32 => 3,
\\ else => 4,
\\ };
2021-06-12 19:48:13 +01:00
\\ _ = b;
\\}
, &.{
":6:14: error: duplicate switch value",
":4:9: note: previous value here",
});
// Ranges not allowed for some kinds of switches.
case.addError(
\\pub export fn main() c_int {
\\ const A: type = i32;
\\ const b: c_int = switch (A) {
\\ i32 => 1,
\\ bool => 2,
\\ f16...f64 => 3,
\\ else => 4,
\\ };
2021-06-12 19:48:13 +01:00
\\ _ = b;
\\}
, &.{
":3:30: error: ranges not allowed when switching on type 'type'",
":6:12: note: range here",
});
// Switch expression has unreachable else prong.
case.addError(
\\pub export fn main() c_int {
\\ var a: u2 = 0;
\\ const b: i32 = switch (a) {
\\ 0 => 10,
\\ 1 => 20,
\\ 2 => 30,
\\ 3 => 40,
\\ else => 50,
\\ };
2021-06-12 19:48:13 +01:00
\\ _ = b;
\\}
, &.{
":8:14: error: unreachable else prong; all cases already handled",
});
2021-01-27 09:40:34 +00:00
}
2021-03-01 16:25:50 +00:00
//{
// var case = ctx.exeFromCompiledC("optionals", .{});
2021-01-28 17:40:53 +00:00
2021-03-01 16:25:50 +00:00
// // Simple while loop
// case.addCompareOutput(
// \\pub export fn main() c_int {
2021-03-01 16:25:50 +00:00
// \\ var count: c_int = 0;
// \\ var opt_ptr: ?*c_int = &count;
// \\ while (opt_ptr) |_| : (count += 1) {
// \\ if (count == 4) opt_ptr = null;
// \\ }
// \\ return count - 5;
// \\}
// , "");
2021-01-30 12:56:36 +00:00
2021-03-01 16:25:50 +00:00
// // Same with non pointer optionals
// case.addCompareOutput(
// \\pub export fn main() c_int {
2021-03-01 16:25:50 +00:00
// \\ var count: c_int = 0;
// \\ var opt_ptr: ?c_int = count;
// \\ while (opt_ptr) |_| : (count += 1) {
// \\ if (count == 4) opt_ptr = null;
// \\ }
// \\ return count - 5;
// \\}
// , "");
//}
{
var case = ctx.exeFromCompiledC("errors", .{});
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var e1 = error.Foo;
\\ var e2 = error.Bar;
\\ assert(e1 != e2);
\\ assert(e1 == error.Foo);
\\ assert(e2 == error.Bar);
\\ return 0;
\\}
\\fn assert(b: bool) void {
\\ if (!b) unreachable;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var e: anyerror!c_int = 0;
\\ const i = e catch 69;
\\ return i;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var e: anyerror!c_int = error.Foo;
\\ const i = e catch 69;
\\ return 69 - i;
\\}
, "");
case.addCompareOutput(
\\const E = error{e};
\\const S = struct { x: u32 };
\\fn f() E!u32 {
\\ const x = (try @as(E!S, S{ .x = 1 })).x;
\\ return x;
\\}
\\pub export fn main() c_int {
\\ const x = f() catch @as(u32, 0);
\\ if (x != 1) unreachable;
\\ return 0;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("structs", .{});
case.addError(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .y = 24,
\\ .x = 12,
\\ .y = 24,
\\ };
\\ return p.y - p.x - p.x;
\\}
, &.{
":6:10: error: duplicate field",
":4:10: note: other field here",
});
case.addError(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .y = 24,
\\ };
\\ return p.y - p.x - p.x;
\\}
, &.{
":3:21: error: missing struct field: x",
":1:15: note: struct 'tmp.Point' declared here",
});
case.addError(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .x = 12,
\\ .y = 24,
\\ .z = 48,
\\ };
\\ return p.y - p.x - p.x;
\\}
, &.{
":6:10: error: no field named 'z' in struct 'tmp.Point'",
":1:15: note: struct declared here",
});
case.addCompareOutput(
\\const Point = struct { x: i32, y: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .x = 12,
\\ .y = 24,
\\ };
\\ return p.y - p.x - p.x;
\\}
, "");
case.addCompareOutput(
\\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 };
\\pub export fn main() c_int {
\\ var p: Point = .{
\\ .x = 18,
\\ .y = 24,
\\ .z = 1,
\\ .a = 2,
\\ .b = 3,
\\ };
\\ return p.y - p.x - p.z - p.a - p.b;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("unions", .{});
case.addError(
\\const U = union {
\\ a: u32,
\\ b
\\};
, &.{
":3:5: error: union field missing type",
});
case.addError(
\\const E = enum { a, b };
\\const U = union(E) {
\\ a: u32 = 1,
\\ b: f32 = 2,
\\};
, &.{
":2:11: error: explicitly valued tagged union requires inferred enum tag type",
":3:14: note: tag value specified here",
});
case.addError(
\\const U = union(enum) {
\\ a: u32 = 1,
\\ b: f32 = 2,
\\};
, &.{
":1:11: error: explicitly valued tagged union missing integer tag type",
":2:14: note: tag value specified here",
});
}
{
var case = ctx.exeFromCompiledC("enums", .{});
case.addError(
\\const E1 = packed enum { a, b, c };
\\const E2 = extern enum { a, b, c };
\\export fn foo() void {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
\\export fn bar() void {
2021-06-12 19:48:13 +01:00
\\ _ = E2.a;
\\}
, &.{
":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
});
// comptime and types are caught in AstGen.
case.addError(
\\const E1 = enum {
\\ a,
\\ comptime b,
\\ c,
\\};
\\const E2 = enum {
\\ a,
\\ b: i32,
\\ c,
\\};
\\export fn foo() void {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
\\export fn bar() void {
2021-06-12 19:48:13 +01:00
\\ _ = E2.a;
\\}
, &.{
":3:5: error: enum fields cannot be marked comptime",
":8:8: error: enum fields do not have types",
":6:12: note: consider 'union(enum)' here to make it a tagged union",
});
// @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch
case.addCompareOutput(
\\const Number = enum { One, Two, Three };
\\
\\pub export fn main() c_int {
\\ var number1 = Number.One;
\\ var number2: Number = .Two;
\\ const number3 = @intToEnum(Number, 2);
\\ if (number1 == number2) return 1;
\\ if (number2 == number3) return 1;
\\ if (@enumToInt(number1) != 0) return 1;
\\ if (@enumToInt(number2) != 1) return 1;
\\ if (@enumToInt(number3) != 2) return 1;
\\ var x: Number = .Two;
\\ if (number2 != x) return 1;
\\ switch (x) {
\\ .One => return 1,
\\ .Two => return 0,
\\ number3 => return 2,
\\ }
\\}
, "");
// Specifying alignment is a parse error.
// This also tests going from a successful build to a parse error.
case.addError(
\\const E1 = enum {
\\ a,
\\ b align(4),
\\ c,
\\};
\\export fn foo() void {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
, &.{
":3:7: error: expected ',' after field",
});
// Redundant non-exhaustive enum mark.
// This also tests going from a parse error to an AstGen error.
case.addError(
\\const E1 = enum {
\\ a,
\\ _,
\\ b,
\\ c,
\\ _,
\\};
\\export fn foo() void {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
, &.{
":6:5: error: redundant non-exhaustive enum mark",
":3:5: note: other mark here",
});
case.addError(
\\const E1 = enum {
\\ a,
\\ b,
\\ c,
\\ _ = 10,
\\};
\\export fn foo() void {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
, &.{
":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
});
case.addError(
\\const E1 = enum {};
\\export fn foo() void {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
, &.{
":1:12: error: enum declarations must have at least one tag",
});
case.addError(
\\const E1 = enum { a, b, _ };
\\export fn foo() void {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
, &.{
":1:12: error: non-exhaustive enum missing integer tag type",
":1:25: note: marked non-exhaustive here",
});
case.addError(
\\const E1 = enum { a, b, c, b, d };
\\pub export fn main() c_int {
2021-06-12 19:48:13 +01:00
\\ _ = E1.a;
\\}
, &.{
":1:28: error: duplicate enum tag",
":1:22: note: other tag here",
});
case.addError(
\\pub export fn main() c_int {
\\ const a = true;
2021-06-12 19:48:13 +01:00
\\ _ = @enumToInt(a);
\\}
, &.{
2021-06-12 19:48:13 +01:00
":3:20: error: expected enum or tagged union, found bool",
});
case.addError(
\\pub export fn main() c_int {
\\ const a = 1;
2021-06-12 19:48:13 +01:00
\\ _ = @intToEnum(bool, a);
\\}
, &.{
2021-06-12 19:48:13 +01:00
":3:20: error: expected enum, found bool",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
2021-06-12 19:48:13 +01:00
\\ _ = @intToEnum(E, 3);
\\}
, &.{
":3:9: error: enum 'tmp.E' has no tag with value 3",
":1:11: note: enum declared here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .c => {},
\\ }
\\}
, &.{
":4:5: error: switch must handle all possibilities",
":4:5: note: unhandled enumeration value: 'b'",
":1:11: note: enum 'tmp.E' declared here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .b => {},
\\ .b => {},
\\ .c => {},
\\ }
\\}
, &.{
":7:10: error: duplicate switch value",
":6:10: note: previous value here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .b => {},
\\ .c => {},
\\ else => {},
\\ }
\\}
, &.{
":8:14: error: unreachable else prong; all cases already handled",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .a;
\\ switch (x) {
\\ .a => {},
\\ .b => {},
\\ _ => {},
\\ }
\\}
, &.{
":4:5: error: '_' prong only allowed when switching on non-exhaustive enums",
":7:11: note: '_' prong here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
2021-06-12 19:48:13 +01:00
\\ _ = E.d;
\\}
, &.{
":3:11: error: enum 'tmp.E' has no member named 'd'",
":1:11: note: enum declared here",
});
case.addError(
\\const E = enum { a, b, c };
\\pub export fn main() c_int {
\\ var x: E = .d;
2021-06-12 19:48:13 +01:00
\\ _ = x;
\\}
, &.{
":3:17: error: enum 'tmp.E' has no field named 'd'",
":1:11: note: enum declared here",
});
}
{
var case = ctx.exeFromCompiledC("shift right + left", .{});
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var i: u32 = 16;
\\ assert(i >> 1, 8);
\\ return 0;
\\}
\\fn assert(a: u32, b: u32) void {
\\ if (a != b) unreachable;
\\}
, "");
case.addCompareOutput(
\\pub export fn main() c_int {
\\ var i: u32 = 16;
\\ assert(i << 1, 32);
\\ return 0;
\\}
\\fn assert(a: u32, b: u32) void {
\\ if (a != b) unreachable;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("inferred error sets", .{});
case.addCompareOutput(
\\pub export fn main() c_int {
\\ if (foo()) |_| {
\\ @panic("test fail");
\\ } else |err| {
\\ if (err != error.ItBroke) {
\\ @panic("test fail");
\\ }
\\ }
\\ return 0;
\\}
\\fn foo() !void {
\\ return error.ItBroke;
\\}
, "");
}
{
// TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64)
var case = ctx.exeFromCompiledC("add/sub wrapping operations", .{});
case.addCompareOutput(
\\pub export fn main() c_int {
\\ // Addition
\\ if (!add_u3(1, 1, 2)) return 1;
\\ if (!add_u3(7, 1, 0)) return 1;
\\ if (!add_i3(1, 1, 2)) return 1;
\\ if (!add_i3(3, 2, -3)) return 1;
\\ if (!add_i3(-3, -2, 3)) return 1;
\\ if (!add_c_int(1, 1, 2)) return 1;
\\ // TODO enable these when stage2 supports std.math.maxInt
\\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1;
\\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1;
\\
\\ // Subtraction
\\ if (!sub_u3(2, 1, 1)) return 1;
\\ if (!sub_u3(0, 1, 7)) return 1;
\\ if (!sub_i3(2, 1, 1)) return 1;
\\ if (!sub_i3(3, -2, -3)) return 1;
\\ if (!sub_i3(-3, 2, 3)) return 1;
\\ if (!sub_c_int(2, 1, 1)) return 1;
\\ // TODO enable these when stage2 supports std.math.maxInt
\\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1;
\\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1;
\\
\\ return 0;
\\}
\\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool {
\\ return expected == lhs +% rhs;
\\}
\\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool {
\\ return expected == lhs +% rhs;
\\}
\\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
\\ return expected == lhs +% rhs;
\\}
\\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool {
\\ return expected == lhs -% rhs;
\\}
\\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool {
\\ return expected == lhs -% rhs;
\\}
\\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
\\ return expected == lhs -% rhs;
\\}
, "");
}
{
var case = ctx.exeFromCompiledC("@rem", linux_x64);
case.addCompareOutput(
\\fn assert(ok: bool) void {
\\ if (!ok) unreachable;
\\}
\\fn rem(lhs: i32, rhs: i32, expected: i32) bool {
\\ return @rem(lhs, rhs) == expected;
\\}
\\pub export fn main() c_int {
\\ assert(rem(-5, 3, -2));
\\ assert(rem(5, 3, 2));
\\ return 0;
\\}
, "");
}
ctx.h("simple header", linux_x64,
\\export fn start() void{}
,
\\ZIG_EXTERN_C void start(void);
\\
);
ctx.h("header with single param function", linux_x64,
2021-06-20 02:10:22 +01:00
\\export fn start(a: u8) void{
\\ _ = a;
\\}
,
\\ZIG_EXTERN_C void start(uint8_t a0);
\\
);
ctx.h("header with multiple param function", linux_x64,
2021-06-20 02:10:22 +01:00
\\export fn start(a: u8, b: u8, c: u8) void{
\\ _ = a; _ = b; _ = c;
\\}
,
\\ZIG_EXTERN_C void start(uint8_t a0, uint8_t a1, uint8_t a2);
\\
);
ctx.h("header with u32 param function", linux_x64,
2021-06-20 02:10:22 +01:00
\\export fn start(a: u32) void{ _ = a; }
,
\\ZIG_EXTERN_C void start(uint32_t a0);
\\
);
ctx.h("header with usize param function", linux_x64,
2021-06-20 02:10:22 +01:00
\\export fn start(a: usize) void{ _ = a; }
,
\\ZIG_EXTERN_C void start(uintptr_t a0);
\\
);
ctx.h("header with bool param function", linux_x64,
2021-06-20 02:10:22 +01:00
\\export fn start(a: bool) void{_ = a;}
,
\\ZIG_EXTERN_C void start(bool a0);
\\
);
ctx.h("header with noreturn function", linux_x64,
\\export fn start() noreturn {
\\ unreachable;
\\}
,
\\ZIG_EXTERN_C zig_noreturn void start(void);
\\
);
ctx.h("header with multiple functions", linux_x64,
\\export fn a() void{}
\\export fn b() void{}
\\export fn c() void{}
,
\\ZIG_EXTERN_C void a(void);
\\ZIG_EXTERN_C void b(void);
\\ZIG_EXTERN_C void c(void);
\\
);
ctx.h("header with multiple includes", linux_x64,
2021-06-20 02:10:22 +01:00
\\export fn start(a: u32, b: usize) void{ _ = a; _ = b; }
,
\\ZIG_EXTERN_C void start(uint32_t a0, uintptr_t a1);
\\
);
}