From d4a0d5f959b88ffc23edc4593bc75b6168acaea9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 15 Mar 2022 15:09:23 -0700 Subject: [PATCH] Sema: implement `@truncate` for SIMD vectors --- src/Compilation.zig | 5 -- src/Sema.zig | 110 ++++++++++++++++++++++------ test/behavior.zig | 6 +- test/behavior/atomics.zig | 89 ++++++++++++++++++++-- test/behavior/bugs/6456.zig | 7 ++ test/behavior/const_slice_child.zig | 9 ++- test/behavior/truncate.zig | 12 ++- 7 files changed, 197 insertions(+), 41 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 5e02d8dba2..c3d349b527 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4513,8 +4513,6 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca const target = comp.getTarget(); const generic_arch_name = target.cpu.arch.genericName(); const use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1; - const stage2_x86_cx16 = target.cpu.arch == .x86_64 and - std.Target.x86.featureSetHas(target.cpu.features, .cx16); const zig_backend: std.builtin.CompilerBackend = blk: { if (use_stage1) break :blk .stage1; @@ -4540,8 +4538,6 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca \\pub const zig_backend = std.builtin.CompilerBackend.{}; \\/// Temporary until self-hosted supports the `cpu.arch` value. \\pub const stage2_arch: std.Target.Cpu.Arch = .{}; - \\/// Temporary until self-hosted can call `std.Target.x86.featureSetHas` at comptime. - \\pub const stage2_x86_cx16 = {}; \\ \\pub const output_mode = std.builtin.OutputMode.{}; \\pub const link_mode = std.builtin.LinkMode.{}; @@ -4557,7 +4553,6 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca build_options.version, std.zig.fmtId(@tagName(zig_backend)), std.zig.fmtId(@tagName(target.cpu.arch)), - stage2_x86_cx16, std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), comp.bin_file.options.is_test, diff --git a/src/Sema.zig b/src/Sema.zig index 118149645d..8fc6b19e46 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -13229,35 +13229,54 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); + const dest_scalar_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); const operand = sema.resolveInst(extra.rhs); + const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_scalar_ty); + const operand_scalar_ty = try sema.checkIntOrVectorAllowComptime(block, operand, operand_src); const operand_ty = sema.typeOf(operand); - const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_ty); - const src_is_comptime_int = try sema.checkIntType(block, operand_src, operand_ty); + const is_vector = operand_ty.zigTypeTag() == .Vector; + const dest_ty = if (is_vector) + try Type.vector(sema.arena, operand_ty.vectorLen(), dest_scalar_ty) + else + dest_scalar_ty; if (dest_is_comptime_int) { return sema.coerce(block, dest_ty, operand, operand_src); } const target = sema.mod.getTarget(); - const dest_info = dest_ty.intInfo(target); + const dest_info = dest_scalar_ty.intInfo(target); if (dest_info.bits == 0) { - return sema.addConstant(dest_ty, Value.zero); - } - - if (!src_is_comptime_int) { - const src_info = operand_ty.intInfo(target); - if (src_info.bits == 0) { + if (is_vector) { + return sema.addConstant( + dest_ty, + try Value.Tag.repeated.create(sema.arena, Value.zero), + ); + } else { return sema.addConstant(dest_ty, Value.zero); } + } - if (src_info.signedness != dest_info.signedness) { + if (operand_scalar_ty.zigTypeTag() != .ComptimeInt) { + const operand_info = operand_ty.intInfo(target); + if (operand_info.bits == 0) { + if (is_vector) { + return sema.addConstant( + dest_ty, + try Value.Tag.repeated.create(sema.arena, Value.zero), + ); + } else { + return sema.addConstant(dest_ty, Value.zero); + } + } + + if (operand_info.signedness != dest_info.signedness) { return sema.fail(block, operand_src, "expected {s} integer type, found '{}'", .{ @tagName(dest_info.signedness), operand_ty, }); } - if (src_info.bits > 0 and src_info.bits < dest_info.bits) { + if (operand_info.bits < dest_info.bits) { const msg = msg: { const msg = try sema.errMsg( block, @@ -13269,8 +13288,8 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai try sema.errNote(block, dest_ty_src, msg, "destination type has {d} bits", .{ dest_info.bits, }); - try sema.errNote(block, operand_src, msg, "source type has {d} bits", .{ - src_info.bits, + try sema.errNote(block, operand_src, msg, "operand type has {d} bits", .{ + operand_info.bits, }); break :msg msg; }; @@ -13280,7 +13299,22 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { if (val.isUndef()) return sema.addConstUndef(dest_ty); - return sema.addConstant(dest_ty, try val.intTrunc(sema.arena, dest_info.signedness, dest_info.bits)); + if (!is_vector) { + return sema.addConstant( + dest_ty, + try val.intTrunc(sema.arena, dest_info.signedness, dest_info.bits), + ); + } + var elem_buf: Value.ElemValueBuffer = undefined; + const elems = try sema.arena.alloc(Value, operand_ty.vectorLen()); + for (elems) |*elem, i| { + const elem_val = val.elemValueBuffer(i, &elem_buf); + elem.* = try elem_val.intTrunc(sema.arena, dest_info.signedness, dest_info.bits); + } + return sema.addConstant( + dest_ty, + try Value.Tag.aggregate.create(sema.arena, elems), + ); } try sema.requireRuntimeBlock(block, src); @@ -13330,13 +13364,13 @@ fn zirBitCount( const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const operand = sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); - try checkIntOrVector(sema, block, operand, operand_src); + _ = try checkIntOrVector(sema, block, operand, operand_src); const target = sema.mod.getTarget(); const bits = operand_ty.intInfo(target).bits; if (bits == 0) { switch (operand_ty.zigTypeTag()) { .Vector => return sema.addConstant( - try Type.vector(sema.arena, operand_ty.arrayLen(), Type.comptime_int), + try Type.vector(sema.arena, operand_ty.vectorLen(), Type.comptime_int), try Value.Tag.repeated.create(sema.arena, Value.zero), ), .Int => return Air.Inst.Ref.zero, @@ -13512,7 +13546,7 @@ fn checkNamespaceType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) Com /// Returns `true` if the type was a comptime_int. fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool { - switch (ty.zigTypeTag()) { + switch (try ty.zigTypeTagOrPoison()) { .ComptimeInt => return true, .Int => return false, else => return sema.fail(block, src, "expected integer type, found '{}'", .{ty}), @@ -13714,11 +13748,43 @@ fn checkIntOrVector( block: *Block, operand: Air.Inst.Ref, operand_src: LazySrcLoc, -) CompileError!void { +) CompileError!Type { const operand_ty = sema.typeOf(operand); - const operand_zig_ty_tag = try operand_ty.zigTypeTagOrPoison(); - switch (operand_zig_ty_tag) { - .Vector, .Int => return, + switch (try operand_ty.zigTypeTagOrPoison()) { + .Int => return operand_ty, + .Vector => { + const elem_ty = operand_ty.childType(); + switch (try elem_ty.zigTypeTagOrPoison()) { + .Int => return elem_ty, + else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{ + elem_ty, + }), + } + }, + else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{ + operand_ty, + }), + } +} + +fn checkIntOrVectorAllowComptime( + sema: *Sema, + block: *Block, + operand: Air.Inst.Ref, + operand_src: LazySrcLoc, +) CompileError!Type { + const operand_ty = sema.typeOf(operand); + switch (try operand_ty.zigTypeTagOrPoison()) { + .Int, .ComptimeInt => return operand_ty, + .Vector => { + const elem_ty = operand_ty.childType(); + switch (try elem_ty.zigTypeTagOrPoison()) { + .Int, .ComptimeInt => return elem_ty, + else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{ + elem_ty, + }), + } + }, else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{ operand_ty, }), diff --git a/test/behavior.zig b/test/behavior.zig index 987c8c3473..9e0cb50558 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -4,6 +4,7 @@ test { _ = @import("behavior/align.zig"); _ = @import("behavior/alignof.zig"); _ = @import("behavior/array.zig"); + _ = @import("behavior/atomics.zig"); _ = @import("behavior/basic.zig"); _ = @import("behavior/bit_shifting.zig"); _ = @import("behavior/bitcast.zig"); @@ -55,6 +56,7 @@ test { _ = @import("behavior/bugs/5413.zig"); _ = @import("behavior/bugs/5474.zig"); _ = @import("behavior/bugs/5487.zig"); + _ = @import("behavior/bugs/6456.zig"); _ = @import("behavior/bugs/6850.zig"); _ = @import("behavior/bugs/7003.zig"); _ = @import("behavior/bugs/7047.zig"); @@ -67,6 +69,7 @@ test { _ = @import("behavior/call.zig"); _ = @import("behavior/cast.zig"); _ = @import("behavior/comptime_memory.zig"); + _ = @import("behavior/const_slice_child.zig"); _ = @import("behavior/defer.zig"); _ = @import("behavior/enum.zig"); _ = @import("behavior/error.zig"); @@ -147,7 +150,6 @@ test { if (builtin.zig_backend != .stage2_c) { // Tests that pass for stage1 and the llvm backend. - _ = @import("behavior/atomics.zig"); _ = @import("behavior/export.zig"); _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/saturating_arithmetic.zig"); @@ -168,10 +170,8 @@ test { _ = @import("behavior/bugs/920.zig"); _ = @import("behavior/bugs/1120.zig"); _ = @import("behavior/bugs/1851.zig"); - _ = @import("behavior/bugs/6456.zig"); _ = @import("behavior/bugs/6781.zig"); _ = @import("behavior/bugs/7027.zig"); - _ = @import("behavior/const_slice_child.zig"); _ = @import("behavior/select.zig"); _ = @import("behavior/struct_contains_slice_of_itself.zig"); _ = @import("behavior/typename.zig"); diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index c642374607..123b4b4886 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -4,6 +4,12 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; test "cmpxchg" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + try testCmpxchg(); comptime try testCmpxchg(); } @@ -26,12 +32,24 @@ fn testCmpxchg() !void { } test "fence" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + var x: i32 = 1234; @fence(.SeqCst); x = 5678; } test "atomicrmw and atomicload" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + var data: u8 = 200; try testAtomicRmw(&data); try expect(data == 42); @@ -55,6 +73,12 @@ fn testAtomicLoad(ptr: *u8) !void { } test "cmpxchg with ptr" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + var data1: i32 = 1234; var data2: i32 = 5678; var data3: i32 = 9101; @@ -75,6 +99,12 @@ test "cmpxchg with ptr" { } test "cmpxchg with ignored result" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + var x: i32 = 1234; _ = @cmpxchgStrong(i32, &x, 1234, 5678, .Monotonic, .Monotonic); @@ -83,19 +113,20 @@ test "cmpxchg with ignored result" { } test "128-bit cmpxchg" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + + if (builtin.cpu.arch != .x86_64) return error.SkipZigTest; + if (comptime !std.Target.x86.featureSetHas(builtin.cpu.features, .cx16)) return error.SkipZigTest; + try test_u128_cmpxchg(); comptime try test_u128_cmpxchg(); } fn test_u128_cmpxchg() !void { - if (builtin.zig_backend != .stage1) { - if (builtin.cpu.arch != .x86_64) return error.SkipZigTest; - if (!builtin.stage2_x86_cx16) return error.SkipZigTest; - } else { - if (builtin.cpu.arch != .x86_64) return error.SkipZigTest; - if (comptime !std.Target.x86.featureSetHas(builtin.cpu.features, .cx16)) return error.SkipZigTest; - } - var x: u128 = 1234; if (@cmpxchgWeak(u128, &x, 99, 5678, .SeqCst, .SeqCst)) |x1| { try expect(x1 == 1234); @@ -115,6 +146,12 @@ fn test_u128_cmpxchg() !void { var a_global_variable = @as(u32, 1234); test "cmpxchg on a global variable" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if ((builtin.zig_backend == .stage1 or builtin.zig_backend == .stage2_llvm) and builtin.cpu.arch == .aarch64) { @@ -127,6 +164,12 @@ test "cmpxchg on a global variable" { } test "atomic load and rmw with enum" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + const Value = enum(u8) { a, b, c }; var x = Value.a; @@ -139,6 +182,12 @@ test "atomic load and rmw with enum" { } test "atomic store" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + var x: u32 = 0; @atomicStore(u32, &x, 1, .SeqCst); try expect(@atomicLoad(u32, &x, .SeqCst) == 1); @@ -147,6 +196,12 @@ test "atomic store" { } test "atomic store comptime" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + comptime try testAtomicStore(); try testAtomicStore(); } @@ -160,6 +215,12 @@ fn testAtomicStore() !void { } test "atomicrmw with floats" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if ((builtin.zig_backend == .stage1 or builtin.zig_backend == .stage2_llvm) and builtin.cpu.arch == .aarch64) { @@ -182,6 +243,12 @@ fn testAtomicRmwFloat() !void { } test "atomicrmw with ints" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + try testAtomicRmwInt(); comptime try testAtomicRmwInt(); } @@ -210,6 +277,12 @@ fn testAtomicRmwInt() !void { } test "atomics with different types" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + try testAtomicsWithType(bool, true, false); try testAtomicsWithType(u1, 0, 1); diff --git a/test/behavior/bugs/6456.zig b/test/behavior/bugs/6456.zig index 3821cccdd0..f42d4f8804 100644 --- a/test/behavior/bugs/6456.zig +++ b/test/behavior/bugs/6456.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const testing = std.testing; const StructField = std.builtin.Type.StructField; @@ -10,6 +11,12 @@ const text = ; test "issue 6456" { + 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + comptime { var fields: []const StructField = &[0]StructField{}; diff --git a/test/behavior/const_slice_child.zig b/test/behavior/const_slice_child.zig index ecce612d1d..2006d6c280 100644 --- a/test/behavior/const_slice_child.zig +++ b/test/behavior/const_slice_child.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const debug = std.debug; const testing = std.testing; @@ -6,6 +7,10 @@ const expect = testing.expect; var argv: [*]const [*]const u8 = undefined; test "const slice child" { + 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 strs = [_][*]const u8{ "one", "two", "three" }; argv = &strs; try bar(strs.len); @@ -19,8 +24,8 @@ fn foo(args: [][]const u8) !void { } fn bar(argc: usize) !void { - const args = testing.allocator.alloc([]const u8, argc) catch unreachable; - defer testing.allocator.free(args); + var args_buffer: [10][]const u8 = undefined; + const args = args_buffer[0..argc]; for (args) |_, i| { const ptr = argv[i]; args[i] = ptr[0..strlen(ptr)]; diff --git a/test/behavior/truncate.zig b/test/behavior/truncate.zig index 7a0213499e..9971fd8804 100644 --- a/test/behavior/truncate.zig +++ b/test/behavior/truncate.zig @@ -62,7 +62,16 @@ test "truncate on comptime integer" { } test "truncate on vectors" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage1) { + // stage1 fails the comptime test + return error.SkipZigTest; + } + + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; const S = struct { fn doTheTest() !void { @@ -71,5 +80,6 @@ test "truncate on vectors" { try expect(std.mem.eql(u8, &@as([4]u8, v2), &[4]u8{ 0xbb, 0xdd, 0xff, 0x22 })); } }; + comptime try S.doTheTest(); try S.doTheTest(); }