zig/test/behavior/var_args.zig
mlugg 407dc6eee4
Liveness: avoid emitting unused instructions or marking their operands as used
Backends want to avoid emitting unused instructions which do not have
side effects: to that end, they all have `Liveness.isUnused` checks for
many instructions. However, checking this in the backends avoids a lot
of potential optimizations. For instance, if a nested field is loaded,
then the first field access would still be emitted, since its result is
used by the next access (which is then unreferenced).

To elide more instructions, Liveness can track this data instead. For
operands which do not have to be lowered (i.e. are not side effecting
and are not something special like `arg), Liveness can ignore their
operand usages, and push the unused information further up, potentially
marking many more instructions as unreferenced.

In doing this, I also uncovered a bug in the LLVM backend relating to
discarding the result of `@cVaArg`, which this change fixes. A behaviour
test has been added to cover it.
2023-04-20 20:28:48 +01:00

234 lines
7.5 KiB
Zig

const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
fn add(args: anytype) i32 {
var sum = @as(i32, 0);
{
comptime var i: usize = 0;
inline while (i < args.len) : (i += 1) {
sum += args[i];
}
}
return sum;
}
test "add arbitrary args" {
try expect(add(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10);
try expect(add(.{@as(i32, 1234)}) == 1234);
try expect(add(.{}) == 0);
}
fn readFirstVarArg(args: anytype) void {
_ = args[0];
}
test "send void arg to var args" {
readFirstVarArg(.{{}});
}
test "pass args directly" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try expect(addSomeStuff(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10);
try expect(addSomeStuff(.{@as(i32, 1234)}) == 1234);
try expect(addSomeStuff(.{}) == 0);
}
fn addSomeStuff(args: anytype) i32 {
return add(args);
}
test "runtime parameter before var args" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
try expect((try extraFn(10, .{})) == 0);
try expect((try extraFn(10, .{false})) == 1);
try expect((try extraFn(10, .{ false, true })) == 2);
comptime {
try expect((try extraFn(10, .{})) == 0);
try expect((try extraFn(10, .{false})) == 1);
try expect((try extraFn(10, .{ false, true })) == 2);
}
}
fn extraFn(extra: u32, args: anytype) !usize {
_ = extra;
if (args.len >= 1) {
try expect(args[0] == false);
}
if (args.len >= 2) {
try expect(args[1] == true);
}
return args.len;
}
const foos = [_]fn (anytype) bool{
foo1,
foo2,
};
fn foo1(args: anytype) bool {
_ = args;
return true;
}
fn foo2(args: anytype) bool {
_ = args;
return false;
}
test "array of var args functions" {
try expect(foos[0](.{}));
try expect(!foos[1](.{}));
}
test "pass zero length array to var args param" {
doNothingWithFirstArg(.{""});
}
fn doNothingWithFirstArg(args: anytype) void {
_ = args[0];
}
test "simple variadic function" {
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_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos) {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
const S = struct {
fn simple(...) callconv(.C) c_int {
var ap = @cVaStart();
defer @cVaEnd(&ap);
return @cVaArg(&ap, c_int);
}
fn compatible(_: c_int, ...) callconv(.C) c_int {
var ap = @cVaStart();
defer @cVaEnd(&ap);
return @cVaArg(&ap, c_int);
}
fn add(count: c_int, ...) callconv(.C) c_int {
var ap = @cVaStart();
defer @cVaEnd(&ap);
var i: usize = 0;
var sum: c_int = 0;
while (i < count) : (i += 1) {
sum += @cVaArg(&ap, c_int);
}
return sum;
}
};
if (builtin.zig_backend != .stage2_c) {
// pre C23 doesn't support varargs without a preceding runtime arg.
try std.testing.expectEqual(@as(c_int, 0), S.simple(@as(c_int, 0)));
try std.testing.expectEqual(@as(c_int, 1024), S.simple(@as(c_int, 1024)));
}
try std.testing.expectEqual(@as(c_int, 0), S.compatible(undefined, @as(c_int, 0)));
try std.testing.expectEqual(@as(c_int, 1024), S.compatible(undefined, @as(c_int, 1024)));
try std.testing.expectEqual(@as(c_int, 0), S.add(0));
try std.testing.expectEqual(@as(c_int, 1), S.add(1, @as(c_int, 1)));
try std.testing.expectEqual(@as(c_int, 3), S.add(2, @as(c_int, 1), @as(c_int, 2)));
}
test "variadic functions" {
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_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos) {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
const S = struct {
fn printf(list_ptr: *std.ArrayList(u8), format: [*:0]const u8, ...) callconv(.C) void {
var ap = @cVaStart();
defer @cVaEnd(&ap);
vprintf(list_ptr, format, &ap);
}
fn vprintf(
list: *std.ArrayList(u8),
format: [*:0]const u8,
ap: *std.builtin.VaList,
) callconv(.C) void {
for (std.mem.span(format)) |c| switch (c) {
's' => {
const arg = @cVaArg(ap, [*:0]const u8);
list.writer().print("{s}", .{arg}) catch return;
},
'd' => {
const arg = @cVaArg(ap, c_int);
list.writer().print("{d}", .{arg}) catch return;
},
else => unreachable,
};
}
};
var list = std.ArrayList(u8).init(std.testing.allocator);
defer list.deinit();
S.printf(&list, "dsd", @as(c_int, 1), @as([*:0]const u8, "hello"), @as(c_int, 5));
try std.testing.expectEqualStrings("1hello5", list.items);
}
test "copy VaList" {
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_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos) {
// https://github.com/ziglang/zig/issues/14096
return error.SkipZigTest;
}
if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest; // TODO
const S = struct {
fn add(count: c_int, ...) callconv(.C) c_int {
var ap = @cVaStart();
defer @cVaEnd(&ap);
var copy = @cVaCopy(&ap);
defer @cVaEnd(&copy);
var i: usize = 0;
var sum: c_int = 0;
while (i < count) : (i += 1) {
sum += @cVaArg(&ap, c_int);
sum += @cVaArg(&copy, c_int) * 2;
}
return sum;
}
};
try std.testing.expectEqual(@as(c_int, 0), S.add(0));
try std.testing.expectEqual(@as(c_int, 3), S.add(1, @as(c_int, 1)));
try std.testing.expectEqual(@as(c_int, 9), S.add(2, @as(c_int, 1), @as(c_int, 2)));
}
test "unused VaList arg" {
const S = struct {
fn thirdArg(dummy: c_int, ...) callconv(.C) c_int {
_ = dummy;
var ap = @cVaStart();
defer @cVaEnd(&ap);
_ = @cVaArg(&ap, c_int);
return @cVaArg(&ap, c_int);
}
};
const x = S.thirdArg(0, @as(c_int, 1), @as(c_int, 2));
try std.testing.expectEqual(@as(c_int, 2), x);
}