authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-06-11 08:21:04+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-06-11 08:21:04+02:00
logd9bd34fd0533295044ffb4160da41f7873aff905
treeff2c582f019497134ad8d81f7bbb422c87d8b3d3
parentd4bc64038ce40ac3829c3f1d0dc21dfb7484818c
parenta567f3871ec06f3e6a8c0e6424aba556f1069ccc
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20247 from Snektron/spirv-vectors-v3

spirv: vectors v3

22 files changed, 1958 insertions(+), 1077 deletions(-)

lib/std/Build.zig+17-3
......@@ -972,10 +972,24 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
972972 // Consider that this is declarative; the run step may not be run unless a user
973973 // option is supplied.
974974 const run_step = Step.Run.create(b, b.fmt("run {s}", .{exe.name}));
975 run_step.addArtifactArg(exe);
975 if (exe.kind == .@"test") {
976 if (exe.exec_cmd_args) |exec_cmd_args| {
977 for (exec_cmd_args) |cmd_arg| {
978 if (cmd_arg) |arg| {
979 run_step.addArg(arg);
980 } else {
981 run_step.addArtifactArg(exe);
982 }
983 }
984 } else {
985 run_step.addArtifactArg(exe);
986 }
976987
977 if (exe.kind == .@"test" and exe.test_server_mode) {
978 run_step.enableTestRunnerMode();
988 if (exe.test_server_mode) {
989 run_step.enableTestRunnerMode();
990 }
991 } else {
992 run_step.addArtifactArg(exe);
979993 }
980994
981995 return run_step;
src/codegen/spirv.zig+1761-1026
......@@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
33const Target = std.Target;
44const log = std.log.scoped(.codegen);
55const assert = std.debug.assert;
6const Signedness = std.builtin.Signedness;
67
78const Module = @import("../Module.zig");
89const Decl = Module.Decl;
......@@ -22,6 +23,7 @@ const IdResultType = spec.IdResultType;
2223const StorageClass = spec.StorageClass;
2324
2425const SpvModule = @import("spirv/Module.zig");
26const IdRange = SpvModule.IdRange;
2527
2628const SpvSection = @import("spirv/Section.zig");
2729const SpvAssembler = @import("spirv/Assembler.zig");
......@@ -32,7 +34,7 @@ pub const zig_call_abi_ver = 3;
3234
3335const InternMap = std.AutoHashMapUnmanaged(struct { InternPool.Index, DeclGen.Repr }, IdResult);
3436const PtrTypeMap = std.AutoHashMapUnmanaged(
35 struct { InternPool.Index, StorageClass },
37 struct { InternPool.Index, StorageClass, DeclGen.Repr },
3638 struct { ty_id: IdRef, fwd_emitted: bool },
3739);
3840
......@@ -422,6 +424,17 @@ const DeclGen = struct {
422424 return self.fail("TODO (SPIR-V): " ++ format, args);
423425 }
424426
427 /// This imports the "default" extended instruction set for the target
428 /// For OpenCL, OpenCL.std.100. For Vulkan, GLSL.std.450.
429 fn importExtendedSet(self: *DeclGen) !IdResult {
430 const target = self.getTarget();
431 return switch (target.os.tag) {
432 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
433 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),
434 else => unreachable,
435 };
436 }
437
425438 /// Fetch the result-id for a previously generated instruction or constant.
426439 fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef {
427440 const mod = self.module;
......@@ -626,10 +639,23 @@ const DeclGen = struct {
626639 }
627640
628641 /// Checks whether the type can be directly translated to SPIR-V vectors
629 fn isVector(self: *DeclGen, ty: Type) bool {
642 fn isSpvVector(self: *DeclGen, ty: Type) bool {
630643 const mod = self.module;
631644 const target = self.getTarget();
632645 if (ty.zigTypeTag(mod) != .Vector) return false;
646
647 // TODO: This check must be expanded for types that can be represented
648 // as integers (enums / packed structs?) and types that are represented
649 // by multiple SPIR-V values.
650 const scalar_ty = ty.scalarType(mod);
651 switch (scalar_ty.zigTypeTag(mod)) {
652 .Bool,
653 .Int,
654 .Float,
655 => {},
656 else => return false,
657 }
658
633659 const elem_ty = ty.childType(mod);
634660
635661 const len = ty.vectorLen(mod);
......@@ -722,9 +748,13 @@ const DeclGen = struct {
722748 // Use backing bits so that negatives are sign extended
723749 const backing_bits = self.backingIntBits(int_info.bits).?; // Assertion failure means big int
724750
725 const bits: u64 = switch (int_info.signedness) {
726 // Intcast needed to silence compile errors for when the wrong path is compiled.
727 // Lazy fix.
751 const signedness: Signedness = switch (@typeInfo(@TypeOf(value))) {
752 .Int => |int| int.signedness,
753 .ComptimeInt => if (value < 0) .signed else .unsigned,
754 else => unreachable,
755 };
756
757 const bits: u64 = switch (signedness) {
728758 .signed => @bitCast(@as(i64, @intCast(value))),
729759 .unsigned => @as(u64, @intCast(value)),
730760 };
......@@ -779,45 +809,51 @@ const DeclGen = struct {
779809 /// Result is in `direct` representation.
780810 fn constructStruct(self: *DeclGen, ty: Type, types: []const Type, constituents: []const IdRef) !IdRef {
781811 assert(types.len == constituents.len);
782 // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
783 // operands are not constant.
784 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
785 // For now, just initialize the struct by setting the fields manually...
786 // TODO: Make this OpCompositeConstruct when we can
787 const ptr_composite_id = try self.alloc(ty, .{ .storage_class = .Function });
788 for (constituents, types, 0..) |constitent_id, member_ty, index| {
789 const ptr_member_ty_id = try self.ptrType(member_ty, .Function);
790 const ptr_id = try self.accessChain(ptr_member_ty_id, ptr_composite_id, &.{@as(u32, @intCast(index))});
791 try self.func.body.emit(self.spv.gpa, .OpStore, .{
792 .pointer = ptr_id,
793 .object = constitent_id,
794 });
795 }
796 return try self.load(ty, ptr_composite_id, .{});
812
813 const result_id = self.spv.allocId();
814 try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{
815 .id_result_type = try self.resolveType(ty, .direct),
816 .id_result = result_id,
817 .constituents = constituents,
818 });
819 return result_id;
797820 }
798821
799822 /// Construct a vector at runtime.
800823 /// ty must be an vector type.
801 /// Constituents should be in `indirect` representation (as the elements of an vector should be).
802 /// Result is in `direct` representation.
803824 fn constructVector(self: *DeclGen, ty: Type, constituents: []const IdRef) !IdRef {
804 // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
805 // operands are not constant.
825 const mod = self.module;
826 assert(ty.vectorLen(mod) == constituents.len);
827
828 // Note: older versions of the Khronos SPRIV-LLVM translator crash on this instruction
829 // because it cannot construct structs which' operands are not constant.
806830 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
807 // For now, just initialize the struct by setting the fields manually...
808 // TODO: Make this OpCompositeConstruct when we can
831 // Currently this is the case for Intel OpenCL CPU runtime (2023-WW46), but the
832 // alternatives dont work properly:
833 // - using temporaries/pointers doesn't work properly with vectors of bool, causes
834 // backends that use llvm to crash
835 // - using OpVectorInsertDynamic doesn't work for non-spirv-vectors of bool.
836
837 const result_id = self.spv.allocId();
838 try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{
839 .id_result_type = try self.resolveType(ty, .direct),
840 .id_result = result_id,
841 .constituents = constituents,
842 });
843 return result_id;
844 }
845
846 /// Construct a vector at runtime with all lanes set to the same value.
847 /// ty must be an vector type.
848 fn constructVectorSplat(self: *DeclGen, ty: Type, constituent: IdRef) !IdRef {
809849 const mod = self.module;
810 const ptr_composite_id = try self.alloc(ty, .{ .storage_class = .Function });
811 const ptr_elem_ty_id = try self.ptrType(ty.elemType2(mod), .Function);
812 for (constituents, 0..) |constitent_id, index| {
813 const ptr_id = try self.accessChain(ptr_elem_ty_id, ptr_composite_id, &.{@as(u32, @intCast(index))});
814 try self.func.body.emit(self.spv.gpa, .OpStore, .{
815 .pointer = ptr_id,
816 .object = constitent_id,
817 });
818 }
850 const n = ty.vectorLen(mod);
819851
820 return try self.load(ty, ptr_composite_id, .{});
852 const constituents = try self.gpa.alloc(IdRef, n);
853 defer self.gpa.free(constituents);
854 @memset(constituents, constituent);
855
856 return try self.constructVector(ty, constituents);
821857 }
822858
823859 /// Construct an array at runtime.
......@@ -825,23 +861,13 @@ const DeclGen = struct {
825861 /// Constituents should be in `indirect` representation (as the elements of an array should be).
826862 /// Result is in `direct` representation.
827863 fn constructArray(self: *DeclGen, ty: Type, constituents: []const IdRef) !IdRef {
828 // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
829 // operands are not constant.
830 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
831 // For now, just initialize the struct by setting the fields manually...
832 // TODO: Make this OpCompositeConstruct when we can
833 const mod = self.module;
834 const ptr_composite_id = try self.alloc(ty, .{ .storage_class = .Function });
835 const ptr_elem_ty_id = try self.ptrType(ty.elemType2(mod), .Function);
836 for (constituents, 0..) |constitent_id, index| {
837 const ptr_id = try self.accessChain(ptr_elem_ty_id, ptr_composite_id, &.{@as(u32, @intCast(index))});
838 try self.func.body.emit(self.spv.gpa, .OpStore, .{
839 .pointer = ptr_id,
840 .object = constitent_id,
841 });
842 }
843
844 return try self.load(ty, ptr_composite_id, .{});
864 const result_id = self.spv.allocId();
865 try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{
866 .id_result_type = try self.resolveType(ty, .direct),
867 .id_result = result_id,
868 .constituents = constituents,
869 });
870 return result_id;
845871 }
846872
847873 /// This function generates a load for a constant in direct (ie, non-memory) representation.
......@@ -1031,21 +1057,27 @@ const DeclGen = struct {
10311057 const constituents = try self.gpa.alloc(IdRef, @intCast(ty.arrayLenIncludingSentinel(mod)));
10321058 defer self.gpa.free(constituents);
10331059
1060 const child_repr: Repr = switch (tag) {
1061 .array_type => .indirect,
1062 .vector_type => .direct,
1063 else => unreachable,
1064 };
1065
10341066 switch (aggregate.storage) {
10351067 .bytes => |bytes| {
10361068 // TODO: This is really space inefficient, perhaps there is a better
10371069 // way to do it?
10381070 for (constituents, bytes.toSlice(constituents.len, ip)) |*constituent, byte| {
1039 constituent.* = try self.constInt(elem_ty, byte, .indirect);
1071 constituent.* = try self.constInt(elem_ty, byte, child_repr);
10401072 }
10411073 },
10421074 .elems => |elems| {
10431075 for (constituents, elems) |*constituent, elem| {
1044 constituent.* = try self.constant(elem_ty, Value.fromInterned(elem), .indirect);
1076 constituent.* = try self.constant(elem_ty, Value.fromInterned(elem), child_repr);
10451077 }
10461078 },
10471079 .repeated_elem => |elem| {
1048 @memset(constituents, try self.constant(elem_ty, Value.fromInterned(elem), .indirect));
1080 @memset(constituents, try self.constant(elem_ty, Value.fromInterned(elem), child_repr));
10491081 },
10501082 }
10511083
......@@ -1334,7 +1366,11 @@ const DeclGen = struct {
13341366 }
13351367
13361368 fn ptrType(self: *DeclGen, child_ty: Type, storage_class: StorageClass) !IdRef {
1337 const key = .{ child_ty.toIntern(), storage_class };
1369 return try self.ptrType2(child_ty, storage_class, .indirect);
1370 }
1371
1372 fn ptrType2(self: *DeclGen, child_ty: Type, storage_class: StorageClass, child_repr: Repr) !IdRef {
1373 const key = .{ child_ty.toIntern(), storage_class, child_repr };
13381374 const entry = try self.ptr_types.getOrPut(self.gpa, key);
13391375 if (entry.found_existing) {
13401376 const fwd_id = entry.value_ptr.ty_id;
......@@ -1354,7 +1390,7 @@ const DeclGen = struct {
13541390 .fwd_emitted = false,
13551391 };
13561392
1357 const child_ty_id = try self.resolveType(child_ty, .indirect);
1393 const child_ty_id = try self.resolveType(child_ty, child_repr);
13581394
13591395 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
13601396 .id_result = result_id,
......@@ -1385,6 +1421,19 @@ const DeclGen = struct {
13851421 return ty_id;
13861422 }
13871423
1424 fn zigScalarOrVectorTypeLike(self: *DeclGen, new_ty: Type, base_ty: Type) !Type {
1425 const mod = self.module;
1426 const new_scalar_ty = new_ty.scalarType(mod);
1427 if (!base_ty.isVector(mod)) {
1428 return new_scalar_ty;
1429 }
1430
1431 return try mod.vectorType(.{
1432 .len = base_ty.vectorLen(mod),
1433 .child = new_scalar_ty.toIntern(),
1434 });
1435 }
1436
13881437 /// Generate a union type. Union types are always generated with the
13891438 /// most aligned field active. If the tag alignment is greater
13901439 /// than that of the payload, a regular union (non-packed, with both tag and
......@@ -1645,11 +1694,10 @@ const DeclGen = struct {
16451694 },
16461695 .Vector => {
16471696 const elem_ty = ty.childType(mod);
1648 // TODO: Make `.direct`.
1649 const elem_ty_id = try self.resolveType(elem_ty, .indirect);
1697 const elem_ty_id = try self.resolveType(elem_ty, repr);
16501698 const len = ty.vectorLen(mod);
16511699
1652 if (self.isVector(ty)) {
1700 if (self.isSpvVector(ty)) {
16531701 return try self.spv.vectorType(len, elem_ty_id);
16541702 } else {
16551703 return try self.arrayType(len, elem_ty_id);
......@@ -1922,81 +1970,897 @@ const DeclGen = struct {
19221970 return union_layout;
19231971 }
19241972
1925 /// This structure is used as helper for element-wise operations. It is intended
1926 /// to be used with vectors, fake vectors (arrays) and single elements.
1927 const WipElementWise = struct {
1928 dg: *DeclGen,
1929 result_ty: Type,
1973 /// This structure represents a "temporary" value: Something we are currently
1974 /// operating on. It typically lives no longer than the function that
1975 /// implements a particular AIR operation. These are used to easier
1976 /// implement vectorizable operations (see Vectorization and the build*
1977 /// functions), and typically are only used for vectors of primitive types.
1978 const Temporary = struct {
1979 /// The type of the temporary. This is here mainly
1980 /// for easier bookkeeping. Because we will never really
1981 /// store Temporaries, they only cause extra stack space,
1982 /// therefore no real storage is wasted.
19301983 ty: Type,
1931 /// Always in direct representation.
1932 ty_id: IdRef,
1933 /// True if the input is an array type.
1934 is_array: bool,
1935 /// The element-wise operation should fill these results before calling finalize().
1936 /// These should all be in **direct** representation! `finalize()` will convert
1937 /// them to indirect if required.
1938 results: []IdRef,
1939
1940 fn deinit(wip: *WipElementWise) void {
1941 wip.dg.gpa.free(wip.results);
1942 }
1943
1944 /// Utility function to extract the element at a particular index in an
1945 /// input array. This type is expected to be a fake vector (array) if `wip.is_array`, and
1946 /// a vector or scalar otherwise.
1947 fn elementAt(wip: WipElementWise, ty: Type, value: IdRef, index: usize) !IdRef {
1948 const mod = wip.dg.module;
1949 if (wip.is_array) {
1950 assert(ty.isVector(mod));
1951 return try wip.dg.extractField(ty.childType(mod), value, @intCast(index));
1984 /// The value that this temporary holds. This is not necessarily
1985 /// a value that is actually usable, or a single value: It is virtual
1986 /// until materialize() is called, at which point is turned into
1987 /// the usual SPIR-V representation of `self.ty`.
1988 value: Temporary.Value,
1989
1990 const Value = union(enum) {
1991 singleton: IdResult,
1992 exploded_vector: IdRange,
1993 };
1994
1995 fn init(ty: Type, singleton: IdResult) Temporary {
1996 return .{ .ty = ty, .value = .{ .singleton = singleton } };
1997 }
1998
1999 fn materialize(self: Temporary, dg: *DeclGen) !IdResult {
2000 const mod = dg.module;
2001 switch (self.value) {
2002 .singleton => |id| return id,
2003 .exploded_vector => |range| {
2004 assert(self.ty.isVector(mod));
2005 assert(self.ty.vectorLen(mod) == range.len);
2006 const consituents = try dg.gpa.alloc(IdRef, range.len);
2007 defer dg.gpa.free(consituents);
2008 for (consituents, 0..range.len) |*id, i| {
2009 id.* = range.at(i);
2010 }
2011 return dg.constructVector(self.ty, consituents);
2012 },
2013 }
2014 }
2015
2016 fn vectorization(self: Temporary, dg: *DeclGen) Vectorization {
2017 return Vectorization.fromType(self.ty, dg);
2018 }
2019
2020 fn pun(self: Temporary, new_ty: Type) Temporary {
2021 return .{
2022 .ty = new_ty,
2023 .value = self.value,
2024 };
2025 }
2026
2027 /// 'Explode' a temporary into separate elements. This turns a vector
2028 /// into a bag of elements.
2029 fn explode(self: Temporary, dg: *DeclGen) !IdRange {
2030 const mod = dg.module;
2031
2032 // If the value is a scalar, then this is a no-op.
2033 if (!self.ty.isVector(mod)) {
2034 return switch (self.value) {
2035 .singleton => |id| IdRange{ .base = @intFromEnum(id), .len = 1 },
2036 .exploded_vector => |range| range,
2037 };
2038 }
2039
2040 const ty_id = try dg.resolveType(self.ty.scalarType(mod), .direct);
2041 const n = self.ty.vectorLen(mod);
2042 const results = dg.spv.allocIds(n);
2043
2044 const id = switch (self.value) {
2045 .singleton => |id| id,
2046 .exploded_vector => |range| return range,
2047 };
2048
2049 for (0..n) |i| {
2050 const indexes = [_]u32{@intCast(i)};
2051 try dg.func.body.emit(dg.spv.gpa, .OpCompositeExtract, .{
2052 .id_result_type = ty_id,
2053 .id_result = results.at(i),
2054 .composite = id,
2055 .indexes = &indexes,
2056 });
2057 }
2058
2059 return results;
2060 }
2061 };
2062
2063 /// Initialize a `Temporary` from an AIR value.
2064 fn temporary(self: *DeclGen, inst: Air.Inst.Ref) !Temporary {
2065 return .{
2066 .ty = self.typeOf(inst),
2067 .value = .{ .singleton = try self.resolve(inst) },
2068 };
2069 }
2070
2071 /// This union describes how a particular operation should be vectorized.
2072 /// That depends on the operation and number of components of the inputs.
2073 const Vectorization = union(enum) {
2074 /// This is an operation between scalars.
2075 scalar,
2076 /// This is an operation between SPIR-V vectors.
2077 /// Value is number of components.
2078 spv_vectorized: u32,
2079 /// This operation is unrolled into separate operations.
2080 /// Inputs may still be SPIR-V vectors, for example,
2081 /// when the operation can't be vectorized in SPIR-V.
2082 /// Value is number of components.
2083 unrolled: u32,
2084
2085 /// Derive a vectorization from a particular type. This usually
2086 /// only checks the size, but the source-of-truth is implemented
2087 /// by `isSpvVector()`.
2088 fn fromType(ty: Type, dg: *DeclGen) Vectorization {
2089 const mod = dg.module;
2090 if (!ty.isVector(mod)) {
2091 return .scalar;
2092 } else if (dg.isSpvVector(ty)) {
2093 return .{ .spv_vectorized = ty.vectorLen(mod) };
19522094 } else {
1953 assert(index == 0);
1954 return value;
2095 return .{ .unrolled = ty.vectorLen(mod) };
19552096 }
19562097 }
19572098
1958 /// Turns the results of this WipElementWise into a result. This can be
1959 /// vectors, fake vectors (arrays) and single elements, depending on `result_ty`.
1960 /// After calling this function, this WIP is no longer usable.
1961 /// Results is in `direct` representation.
1962 fn finalize(wip: *WipElementWise) !IdRef {
1963 if (wip.is_array) {
1964 // Convert all the constituents to indirect, as required for the array.
1965 for (wip.results) |*result| {
1966 result.* = try wip.dg.convertToIndirect(wip.ty, result.*);
2099 /// Given two vectorization methods, compute a "unification": a fallback
2100 /// that works for both, according to the following rules:
2101 /// - Scalars may broadcast
2102 /// - SPIR-V vectorized operations may unroll
2103 /// - Prefer scalar > SPIR-V vectorized > unrolled
2104 fn unify(a: Vectorization, b: Vectorization) Vectorization {
2105 if (a == .scalar and b == .scalar) {
2106 return .scalar;
2107 } else if (a == .spv_vectorized and b == .spv_vectorized) {
2108 assert(a.components() == b.components());
2109 return .{ .spv_vectorized = a.components() };
2110 } else if (a == .unrolled or b == .unrolled) {
2111 if (a == .unrolled and b == .unrolled) {
2112 assert(a.components() == b.components());
2113 return .{ .unrolled = a.components() };
2114 } else if (a == .unrolled) {
2115 return .{ .unrolled = a.components() };
2116 } else if (b == .unrolled) {
2117 return .{ .unrolled = b.components() };
2118 } else {
2119 unreachable;
19672120 }
1968 return try wip.dg.constructArray(wip.result_ty, wip.results);
19692121 } else {
1970 return wip.results[0];
2122 if (a == .spv_vectorized) {
2123 return .{ .spv_vectorized = a.components() };
2124 } else if (b == .spv_vectorized) {
2125 return .{ .spv_vectorized = b.components() };
2126 } else {
2127 unreachable;
2128 }
19712129 }
19722130 }
19732131
1974 /// Allocate a result id at a particular index, and return it.
1975 fn allocId(wip: *WipElementWise, index: usize) IdRef {
1976 assert(wip.is_array or index == 0);
1977 wip.results[index] = wip.dg.spv.allocId();
1978 return wip.results[index];
2132 /// Force this vectorization to be unrolled, if its
2133 /// an operation involving vectors.
2134 fn unroll(self: Vectorization) Vectorization {
2135 return switch (self) {
2136 .scalar, .unrolled => self,
2137 .spv_vectorized => |n| .{ .unrolled = n },
2138 };
19792139 }
2140
2141 /// Query the number of components that inputs of this operation have.
2142 /// Note: for broadcasting scalars, this returns the number of elements
2143 /// that the broadcasted vector would have.
2144 fn components(self: Vectorization) u32 {
2145 return switch (self) {
2146 .scalar => 1,
2147 .spv_vectorized => |n| n,
2148 .unrolled => |n| n,
2149 };
2150 }
2151
2152 /// Query the number of operations involving this vectorization.
2153 /// This is basically the number of components, except that SPIR-V vectorized
2154 /// operations only need a single SPIR-V instruction.
2155 fn operations(self: Vectorization) u32 {
2156 return switch (self) {
2157 .scalar, .spv_vectorized => 1,
2158 .unrolled => |n| n,
2159 };
2160 }
2161
2162 /// Turns `ty` into the result-type of an individual vector operation.
2163 /// `ty` may be a scalar or vector, it doesn't matter.
2164 fn operationType(self: Vectorization, dg: *DeclGen, ty: Type) !Type {
2165 const mod = dg.module;
2166 const scalar_ty = ty.scalarType(mod);
2167 return switch (self) {
2168 .scalar, .unrolled => scalar_ty,
2169 .spv_vectorized => |n| try mod.vectorType(.{
2170 .len = n,
2171 .child = scalar_ty.toIntern(),
2172 }),
2173 };
2174 }
2175
2176 /// Turns `ty` into the result-type of the entire operation.
2177 /// `ty` may be a scalar or vector, it doesn't matter.
2178 fn resultType(self: Vectorization, dg: *DeclGen, ty: Type) !Type {
2179 const mod = dg.module;
2180 const scalar_ty = ty.scalarType(mod);
2181 return switch (self) {
2182 .scalar => scalar_ty,
2183 .unrolled, .spv_vectorized => |n| try mod.vectorType(.{
2184 .len = n,
2185 .child = scalar_ty.toIntern(),
2186 }),
2187 };
2188 }
2189
2190 /// Before a temporary can be used, some setup may need to be one. This function implements
2191 /// this setup, and returns a new type that holds the relevant information on how to access
2192 /// elements of the input.
2193 fn prepare(self: Vectorization, dg: *DeclGen, tmp: Temporary) !PreparedOperand {
2194 const mod = dg.module;
2195 const is_vector = tmp.ty.isVector(mod);
2196 const is_spv_vector = dg.isSpvVector(tmp.ty);
2197 const value: PreparedOperand.Value = switch (tmp.value) {
2198 .singleton => |id| switch (self) {
2199 .scalar => blk: {
2200 assert(!is_vector);
2201 break :blk .{ .scalar = id };
2202 },
2203 .spv_vectorized => blk: {
2204 if (is_vector) {
2205 assert(is_spv_vector);
2206 break :blk .{ .spv_vectorwise = id };
2207 }
2208
2209 // Broadcast scalar into vector.
2210 const vector_ty = try mod.vectorType(.{
2211 .len = self.components(),
2212 .child = tmp.ty.toIntern(),
2213 });
2214
2215 const vector = try dg.constructVectorSplat(vector_ty, id);
2216 return .{
2217 .ty = vector_ty,
2218 .value = .{ .spv_vectorwise = vector },
2219 };
2220 },
2221 .unrolled => blk: {
2222 if (is_vector) {
2223 break :blk .{ .vector_exploded = try tmp.explode(dg) };
2224 } else {
2225 break :blk .{ .scalar_broadcast = id };
2226 }
2227 },
2228 },
2229 .exploded_vector => |range| switch (self) {
2230 .scalar => unreachable,
2231 .spv_vectorized => |n| blk: {
2232 // We can vectorize this operation, but we have an exploded vector. This can happen
2233 // when a vectorizable operation succeeds a non-vectorizable operation. In this case,
2234 // pack up the IDs into a SPIR-V vector. This path should not be able to be hit with
2235 // a type that cannot do that.
2236 assert(is_spv_vector);
2237 assert(range.len == n);
2238 const vec = try tmp.materialize(dg);
2239 break :blk .{ .spv_vectorwise = vec };
2240 },
2241 .unrolled => |n| blk: {
2242 assert(range.len == n);
2243 break :blk .{ .vector_exploded = range };
2244 },
2245 },
2246 };
2247
2248 return .{
2249 .ty = tmp.ty,
2250 .value = value,
2251 };
2252 }
2253
2254 /// Finalize the results of an operation back into a temporary. `results` is
2255 /// a list of result-ids of the operation.
2256 fn finalize(self: Vectorization, ty: Type, results: IdRange) Temporary {
2257 assert(self.operations() == results.len);
2258 const value: Temporary.Value = switch (self) {
2259 .scalar, .spv_vectorized => blk: {
2260 break :blk .{ .singleton = results.at(0) };
2261 },
2262 .unrolled => blk: {
2263 break :blk .{ .exploded_vector = results };
2264 },
2265 };
2266
2267 return .{ .ty = ty, .value = value };
2268 }
2269
2270 /// This struct represents an operand that has gone through some setup, and is
2271 /// ready to be used as part of an operation.
2272 const PreparedOperand = struct {
2273 ty: Type,
2274 value: PreparedOperand.Value,
2275
2276 /// The types of value that a prepared operand can hold internally. Depends
2277 /// on the operation and input value.
2278 const Value = union(enum) {
2279 /// A single scalar value that is used by a scalar operation.
2280 scalar: IdResult,
2281 /// A single scalar that is broadcasted in an unrolled operation.
2282 scalar_broadcast: IdResult,
2283 /// A SPIR-V vector that is used in SPIR-V vectorize operation.
2284 spv_vectorwise: IdResult,
2285 /// A vector represented by a consecutive list of IDs that is used in an unrolled operation.
2286 vector_exploded: IdRange,
2287 };
2288
2289 /// Query the value at a particular index of the operation. Note that
2290 /// the index is *not* the component/lane, but the index of the *operation*. When
2291 /// this operation is vectorized, the return value of this function is a SPIR-V vector.
2292 /// See also `Vectorization.operations()`.
2293 fn at(self: PreparedOperand, i: usize) IdResult {
2294 switch (self.value) {
2295 .scalar => |id| {
2296 assert(i == 0);
2297 return id;
2298 },
2299 .scalar_broadcast => |id| {
2300 return id;
2301 },
2302 .spv_vectorwise => |id| {
2303 assert(i == 0);
2304 return id;
2305 },
2306 .vector_exploded => |range| {
2307 return range.at(i);
2308 },
2309 }
2310 }
2311 };
19802312 };
19812313
1982 /// Create a new element-wise operation.
1983 fn elementWise(self: *DeclGen, result_ty: Type, force_element_wise: bool) !WipElementWise {
2314 /// A utility function to compute the vectorization style of
2315 /// a list of values. These values may be any of the following:
2316 /// - A `Vectorization` instance
2317 /// - A Type, in which case the vectorization is computed via `Vectorization.fromType`.
2318 /// - A Temporary, in which case the vectorization is computed via `Temporary.vectorization`.
2319 fn vectorization(self: *DeclGen, args: anytype) Vectorization {
2320 var v: Vectorization = undefined;
2321 assert(args.len >= 1);
2322 inline for (args, 0..) |arg, i| {
2323 const iv: Vectorization = switch (@TypeOf(arg)) {
2324 Vectorization => arg,
2325 Type => Vectorization.fromType(arg, self),
2326 Temporary => arg.vectorization(self),
2327 else => @compileError("invalid type"),
2328 };
2329 if (i == 0) {
2330 v = iv;
2331 } else {
2332 v = v.unify(iv);
2333 }
2334 }
2335 return v;
2336 }
2337
2338 /// This function builds an OpSConvert of OpUConvert depending on the
2339 /// signedness of the types.
2340 fn buildIntConvert(self: *DeclGen, dst_ty: Type, src: Temporary) !Temporary {
19842341 const mod = self.module;
1985 const is_array = result_ty.isVector(mod) and (!self.isVector(result_ty) or force_element_wise);
1986 const num_results = if (is_array) result_ty.vectorLen(mod) else 1;
1987 const results = try self.gpa.alloc(IdRef, num_results);
1988 @memset(results, undefined);
19892342
1990 const ty = if (is_array) result_ty.scalarType(mod) else result_ty;
1991 const ty_id = try self.resolveType(ty, .direct);
2343 const dst_ty_id = try self.resolveType(dst_ty.scalarType(mod), .direct);
2344 const src_ty_id = try self.resolveType(src.ty.scalarType(mod), .direct);
2345
2346 const v = self.vectorization(.{ dst_ty, src });
2347 const result_ty = try v.resultType(self, dst_ty);
2348
2349 // We can directly compare integers, because those type-IDs are cached.
2350 if (dst_ty_id == src_ty_id) {
2351 // Nothing to do, type-pun to the right value.
2352 // Note, Caller guarantees that the types fit (or caller will normalize after),
2353 // so we don't have to normalize here.
2354 // Note, dst_ty may be a scalar type even if we expect a vector, so we have to
2355 // convert to the right type here.
2356 return src.pun(result_ty);
2357 }
2358
2359 const ops = v.operations();
2360 const results = self.spv.allocIds(ops);
2361
2362 const op_result_ty = try v.operationType(self, dst_ty);
2363 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2364
2365 const opcode: Opcode = if (dst_ty.isSignedInt(mod)) .OpSConvert else .OpUConvert;
2366
2367 const op_src = try v.prepare(self, src);
2368
2369 for (0..ops) |i| {
2370 try self.func.body.emitRaw(self.spv.gpa, opcode, 3);
2371 self.func.body.writeOperand(spec.IdResultType, op_result_ty_id);
2372 self.func.body.writeOperand(IdResult, results.at(i));
2373 self.func.body.writeOperand(IdResult, op_src.at(i));
2374 }
2375
2376 return v.finalize(result_ty, results);
2377 }
2378
2379 fn buildFma(self: *DeclGen, a: Temporary, b: Temporary, c: Temporary) !Temporary {
2380 const target = self.getTarget();
2381
2382 const v = self.vectorization(.{ a, b, c });
2383 const ops = v.operations();
2384 const results = self.spv.allocIds(ops);
2385
2386 const op_result_ty = try v.operationType(self, a.ty);
2387 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2388 const result_ty = try v.resultType(self, a.ty);
2389
2390 const op_a = try v.prepare(self, a);
2391 const op_b = try v.prepare(self, b);
2392 const op_c = try v.prepare(self, c);
2393
2394 const set = try self.importExtendedSet();
2395
2396 // TODO: Put these numbers in some definition
2397 const instruction: u32 = switch (target.os.tag) {
2398 .opencl => 26, // fma
2399 // NOTE: Vulkan's FMA instruction does *NOT* produce the right values!
2400 // its precision guarantees do NOT match zigs and it does NOT match OpenCLs!
2401 // it needs to be emulated!
2402 .vulkan => unreachable, // TODO: See above
2403 else => unreachable,
2404 };
2405
2406 for (0..ops) |i| {
2407 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2408 .id_result_type = op_result_ty_id,
2409 .id_result = results.at(i),
2410 .set = set,
2411 .instruction = .{ .inst = instruction },
2412 .id_ref_4 = &.{ op_a.at(i), op_b.at(i), op_c.at(i) },
2413 });
2414 }
2415
2416 return v.finalize(result_ty, results);
2417 }
2418
2419 fn buildSelect(self: *DeclGen, condition: Temporary, lhs: Temporary, rhs: Temporary) !Temporary {
2420 const mod = self.module;
2421
2422 const v = self.vectorization(.{ condition, lhs, rhs });
2423 const ops = v.operations();
2424 const results = self.spv.allocIds(ops);
2425
2426 const op_result_ty = try v.operationType(self, lhs.ty);
2427 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2428 const result_ty = try v.resultType(self, lhs.ty);
2429
2430 assert(condition.ty.scalarType(mod).zigTypeTag(mod) == .Bool);
2431
2432 const cond = try v.prepare(self, condition);
2433 const object_1 = try v.prepare(self, lhs);
2434 const object_2 = try v.prepare(self, rhs);
2435
2436 for (0..ops) |i| {
2437 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2438 .id_result_type = op_result_ty_id,
2439 .id_result = results.at(i),
2440 .condition = cond.at(i),
2441 .object_1 = object_1.at(i),
2442 .object_2 = object_2.at(i),
2443 });
2444 }
2445
2446 return v.finalize(result_ty, results);
2447 }
2448
2449 const CmpPredicate = enum {
2450 l_eq,
2451 l_ne,
2452 i_ne,
2453 i_eq,
2454 s_lt,
2455 s_gt,
2456 s_le,
2457 s_ge,
2458 u_lt,
2459 u_gt,
2460 u_le,
2461 u_ge,
2462 f_oeq,
2463 f_une,
2464 f_olt,
2465 f_ole,
2466 f_ogt,
2467 f_oge,
2468 };
2469
2470 fn buildCmp(self: *DeclGen, pred: CmpPredicate, lhs: Temporary, rhs: Temporary) !Temporary {
2471 const v = self.vectorization(.{ lhs, rhs });
2472 const ops = v.operations();
2473 const results = self.spv.allocIds(ops);
2474
2475 const op_result_ty = try v.operationType(self, Type.bool);
2476 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2477 const result_ty = try v.resultType(self, Type.bool);
2478
2479 const op_lhs = try v.prepare(self, lhs);
2480 const op_rhs = try v.prepare(self, rhs);
2481
2482 const opcode: Opcode = switch (pred) {
2483 .l_eq => .OpLogicalEqual,
2484 .l_ne => .OpLogicalNotEqual,
2485 .i_eq => .OpIEqual,
2486 .i_ne => .OpINotEqual,
2487 .s_lt => .OpSLessThan,
2488 .s_gt => .OpSGreaterThan,
2489 .s_le => .OpSLessThanEqual,
2490 .s_ge => .OpSGreaterThanEqual,
2491 .u_lt => .OpULessThan,
2492 .u_gt => .OpUGreaterThan,
2493 .u_le => .OpULessThanEqual,
2494 .u_ge => .OpUGreaterThanEqual,
2495 .f_oeq => .OpFOrdEqual,
2496 .f_une => .OpFUnordNotEqual,
2497 .f_olt => .OpFOrdLessThan,
2498 .f_ole => .OpFOrdLessThanEqual,
2499 .f_ogt => .OpFOrdGreaterThan,
2500 .f_oge => .OpFOrdGreaterThanEqual,
2501 };
2502
2503 for (0..ops) |i| {
2504 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
2505 self.func.body.writeOperand(spec.IdResultType, op_result_ty_id);
2506 self.func.body.writeOperand(IdResult, results.at(i));
2507 self.func.body.writeOperand(IdResult, op_lhs.at(i));
2508 self.func.body.writeOperand(IdResult, op_rhs.at(i));
2509 }
2510
2511 return v.finalize(result_ty, results);
2512 }
2513
2514 const UnaryOp = enum {
2515 l_not,
2516 bit_not,
2517 i_neg,
2518 f_neg,
2519 i_abs,
2520 f_abs,
2521 clz,
2522 ctz,
2523 floor,
2524 ceil,
2525 trunc,
2526 round,
2527 sqrt,
2528 sin,
2529 cos,
2530 tan,
2531 exp,
2532 exp2,
2533 log,
2534 log2,
2535 log10,
2536 };
2537
2538 fn buildUnary(self: *DeclGen, op: UnaryOp, operand: Temporary) !Temporary {
2539 const target = self.getTarget();
2540 const v = blk: {
2541 const v = self.vectorization(.{operand});
2542 break :blk switch (op) {
2543 // TODO: These instructions don't seem to be working
2544 // properly for LLVM-based backends on OpenCL for 8- and
2545 // 16-component vectors.
2546 .i_abs => if (target.os.tag == .opencl and v.components() >= 8) v.unroll() else v,
2547 else => v,
2548 };
2549 };
2550
2551 const ops = v.operations();
2552 const results = self.spv.allocIds(ops);
2553
2554 const op_result_ty = try v.operationType(self, operand.ty);
2555 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2556 const result_ty = try v.resultType(self, operand.ty);
2557
2558 const op_operand = try v.prepare(self, operand);
2559
2560 if (switch (op) {
2561 .l_not => .OpLogicalNot,
2562 .bit_not => .OpNot,
2563 .i_neg => .OpSNegate,
2564 .f_neg => .OpFNegate,
2565 else => @as(?Opcode, null),
2566 }) |opcode| {
2567 for (0..ops) |i| {
2568 try self.func.body.emitRaw(self.spv.gpa, opcode, 3);
2569 self.func.body.writeOperand(spec.IdResultType, op_result_ty_id);
2570 self.func.body.writeOperand(IdResult, results.at(i));
2571 self.func.body.writeOperand(IdResult, op_operand.at(i));
2572 }
2573 } else {
2574 const set = try self.importExtendedSet();
2575 const extinst: u32 = switch (target.os.tag) {
2576 .opencl => switch (op) {
2577 .i_abs => 141, // s_abs
2578 .f_abs => 23, // fabs
2579 .clz => 151, // clz
2580 .ctz => 152, // ctz
2581 .floor => 25, // floor
2582 .ceil => 12, // ceil
2583 .trunc => 66, // trunc
2584 .round => 55, // round
2585 .sqrt => 61, // sqrt
2586 .sin => 57, // sin
2587 .cos => 14, // cos
2588 .tan => 62, // tan
2589 .exp => 19, // exp
2590 .exp2 => 20, // exp2
2591 .log => 37, // log
2592 .log2 => 38, // log2
2593 .log10 => 39, // log10
2594 else => unreachable,
2595 },
2596 // Note: We'll need to check these for floating point accuracy
2597 // Vulkan does not put tight requirements on these, for correction
2598 // we might want to emulate them at some point.
2599 .vulkan => switch (op) {
2600 .i_abs => 5, // SAbs
2601 .f_abs => 4, // FAbs
2602 .clz => unreachable, // TODO
2603 .ctz => unreachable, // TODO
2604 .floor => 8, // Floor
2605 .ceil => 9, // Ceil
2606 .trunc => 3, // Trunc
2607 .round => 1, // Round
2608 .sqrt,
2609 .sin,
2610 .cos,
2611 .tan,
2612 .exp,
2613 .exp2,
2614 .log,
2615 .log2,
2616 .log10,
2617 => unreachable, // TODO
2618 else => unreachable,
2619 },
2620 else => unreachable,
2621 };
2622
2623 for (0..ops) |i| {
2624 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2625 .id_result_type = op_result_ty_id,
2626 .id_result = results.at(i),
2627 .set = set,
2628 .instruction = .{ .inst = extinst },
2629 .id_ref_4 = &.{op_operand.at(i)},
2630 });
2631 }
2632 }
2633
2634 return v.finalize(result_ty, results);
2635 }
2636
2637 const BinaryOp = enum {
2638 i_add,
2639 f_add,
2640 i_sub,
2641 f_sub,
2642 i_mul,
2643 f_mul,
2644 s_div,
2645 u_div,
2646 f_div,
2647 s_rem,
2648 f_rem,
2649 s_mod,
2650 u_mod,
2651 f_mod,
2652 srl,
2653 sra,
2654 sll,
2655 bit_and,
2656 bit_or,
2657 bit_xor,
2658 f_max,
2659 s_max,
2660 u_max,
2661 f_min,
2662 s_min,
2663 u_min,
2664 l_and,
2665 l_or,
2666 };
2667
2668 fn buildBinary(self: *DeclGen, op: BinaryOp, lhs: Temporary, rhs: Temporary) !Temporary {
2669 const target = self.getTarget();
19922670
2671 const v = self.vectorization(.{ lhs, rhs });
2672 const ops = v.operations();
2673 const results = self.spv.allocIds(ops);
2674
2675 const op_result_ty = try v.operationType(self, lhs.ty);
2676 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2677 const result_ty = try v.resultType(self, lhs.ty);
2678
2679 const op_lhs = try v.prepare(self, lhs);
2680 const op_rhs = try v.prepare(self, rhs);
2681
2682 if (switch (op) {
2683 .i_add => .OpIAdd,
2684 .f_add => .OpFAdd,
2685 .i_sub => .OpISub,
2686 .f_sub => .OpFSub,
2687 .i_mul => .OpIMul,
2688 .f_mul => .OpFMul,
2689 .s_div => .OpSDiv,
2690 .u_div => .OpUDiv,
2691 .f_div => .OpFDiv,
2692 .s_rem => .OpSRem,
2693 .f_rem => .OpFRem,
2694 .s_mod => .OpSMod,
2695 .u_mod => .OpUMod,
2696 .f_mod => .OpFMod,
2697 .srl => .OpShiftRightLogical,
2698 .sra => .OpShiftRightArithmetic,
2699 .sll => .OpShiftLeftLogical,
2700 .bit_and => .OpBitwiseAnd,
2701 .bit_or => .OpBitwiseOr,
2702 .bit_xor => .OpBitwiseXor,
2703 .l_and => .OpLogicalAnd,
2704 .l_or => .OpLogicalOr,
2705 else => @as(?Opcode, null),
2706 }) |opcode| {
2707 for (0..ops) |i| {
2708 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
2709 self.func.body.writeOperand(spec.IdResultType, op_result_ty_id);
2710 self.func.body.writeOperand(IdResult, results.at(i));
2711 self.func.body.writeOperand(IdResult, op_lhs.at(i));
2712 self.func.body.writeOperand(IdResult, op_rhs.at(i));
2713 }
2714 } else {
2715 const set = try self.importExtendedSet();
2716
2717 // TODO: Put these numbers in some definition
2718 const extinst: u32 = switch (target.os.tag) {
2719 .opencl => switch (op) {
2720 .f_max => 27, // fmax
2721 .s_max => 156, // s_max
2722 .u_max => 157, // u_max
2723 .f_min => 28, // fmin
2724 .s_min => 158, // s_min
2725 .u_min => 159, // u_min
2726 else => unreachable,
2727 },
2728 .vulkan => switch (op) {
2729 .f_max => 40, // FMax
2730 .s_max => 42, // SMax
2731 .u_max => 41, // UMax
2732 .f_min => 37, // FMin
2733 .s_min => 39, // SMin
2734 .u_min => 38, // UMin
2735 else => unreachable,
2736 },
2737 else => unreachable,
2738 };
2739
2740 for (0..ops) |i| {
2741 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2742 .id_result_type = op_result_ty_id,
2743 .id_result = results.at(i),
2744 .set = set,
2745 .instruction = .{ .inst = extinst },
2746 .id_ref_4 = &.{ op_lhs.at(i), op_rhs.at(i) },
2747 });
2748 }
2749 }
2750
2751 return v.finalize(result_ty, results);
2752 }
2753
2754 /// This function builds an extended multiplication, either OpSMulExtended or OpUMulExtended on Vulkan,
2755 /// or OpIMul and s_mul_hi or u_mul_hi on OpenCL.
2756 fn buildWideMul(
2757 self: *DeclGen,
2758 op: enum {
2759 s_mul_extended,
2760 u_mul_extended,
2761 },
2762 lhs: Temporary,
2763 rhs: Temporary,
2764 ) !struct { Temporary, Temporary } {
2765 const mod = self.module;
2766 const target = self.getTarget();
2767 const ip = &mod.intern_pool;
2768
2769 const v = lhs.vectorization(self).unify(rhs.vectorization(self));
2770 const ops = v.operations();
2771
2772 const arith_op_ty = try v.operationType(self, lhs.ty);
2773 const arith_op_ty_id = try self.resolveType(arith_op_ty, .direct);
2774
2775 const lhs_op = try v.prepare(self, lhs);
2776 const rhs_op = try v.prepare(self, rhs);
2777
2778 const value_results = self.spv.allocIds(ops);
2779 const overflow_results = self.spv.allocIds(ops);
2780
2781 switch (target.os.tag) {
2782 .opencl => {
2783 // Currently, SPIRV-LLVM-Translator based backends cannot deal with OpSMulExtended and
2784 // OpUMulExtended. For these we will use the OpenCL s_mul_hi to compute the high-order bits
2785 // instead.
2786 const set = try self.importExtendedSet();
2787 const overflow_inst: u32 = switch (op) {
2788 .s_mul_extended => 160, // s_mul_hi
2789 .u_mul_extended => 203, // u_mul_hi
2790 };
2791
2792 for (0..ops) |i| {
2793 try self.func.body.emit(self.spv.gpa, .OpIMul, .{
2794 .id_result_type = arith_op_ty_id,
2795 .id_result = value_results.at(i),
2796 .operand_1 = lhs_op.at(i),
2797 .operand_2 = rhs_op.at(i),
2798 });
2799
2800 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2801 .id_result_type = arith_op_ty_id,
2802 .id_result = overflow_results.at(i),
2803 .set = set,
2804 .instruction = .{ .inst = overflow_inst },
2805 .id_ref_4 = &.{ lhs_op.at(i), rhs_op.at(i) },
2806 });
2807 }
2808 },
2809 .vulkan => {
2810 const op_result_ty = blk: {
2811 // Operations return a struct{T, T}
2812 // where T is maybe vectorized.
2813 const types = [2]InternPool.Index{ arith_op_ty.toIntern(), arith_op_ty.toIntern() };
2814 const values = [2]InternPool.Index{ .none, .none };
2815 const index = try ip.getAnonStructType(mod.gpa, .{
2816 .types = &types,
2817 .values = &values,
2818 .names = &.{},
2819 });
2820 break :blk Type.fromInterned(index);
2821 };
2822 const op_result_ty_id = try self.resolveType(op_result_ty, .direct);
2823
2824 const opcode: Opcode = switch (op) {
2825 .s_mul_extended => .OpSMulExtended,
2826 .u_mul_extended => .OpUMulExtended,
2827 };
2828
2829 for (0..ops) |i| {
2830 const op_result = self.spv.allocId();
2831
2832 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
2833 self.func.body.writeOperand(spec.IdResultType, op_result_ty_id);
2834 self.func.body.writeOperand(IdResult, op_result);
2835 self.func.body.writeOperand(IdResult, lhs_op.at(i));
2836 self.func.body.writeOperand(IdResult, rhs_op.at(i));
2837
2838 // The above operation returns a struct. We might want to expand
2839 // Temporary to deal with the fact that these are structs eventually,
2840 // but for now, take the struct apart and return two separate vectors.
2841
2842 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
2843 .id_result_type = arith_op_ty_id,
2844 .id_result = value_results.at(i),
2845 .composite = op_result,
2846 .indexes = &.{0},
2847 });
2848
2849 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
2850 .id_result_type = arith_op_ty_id,
2851 .id_result = overflow_results.at(i),
2852 .composite = op_result,
2853 .indexes = &.{1},
2854 });
2855 }
2856 },
2857 else => unreachable,
2858 }
2859
2860 const result_ty = try v.resultType(self, lhs.ty);
19932861 return .{
1994 .dg = self,
1995 .result_ty = result_ty,
1996 .ty = ty,
1997 .ty_id = ty_id,
1998 .is_array = is_array,
1999 .results = results,
2862 v.finalize(result_ty, value_results),
2863 v.finalize(result_ty, overflow_results),
20002864 };
20012865 }
20022866
......@@ -2235,47 +3099,58 @@ const DeclGen = struct {
22353099 }
22363100 }
22373101
2238 fn intFromBool(self: *DeclGen, ty: Type, condition_id: IdRef) !IdRef {
2239 const zero_id = try self.constInt(ty, 0, .direct);
2240 const one_id = try self.constInt(ty, 1, .direct);
2241 const result_id = self.spv.allocId();
2242 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2243 .id_result_type = try self.resolveType(ty, .direct),
2244 .id_result = result_id,
2245 .condition = condition_id,
2246 .object_1 = one_id,
2247 .object_2 = zero_id,
2248 });
2249 return result_id;
3102 fn intFromBool(self: *DeclGen, value: Temporary) !Temporary {
3103 return try self.intFromBool2(value, Type.u1);
3104 }
3105
3106 fn intFromBool2(self: *DeclGen, value: Temporary, result_ty: Type) !Temporary {
3107 const zero_id = try self.constInt(result_ty, 0, .direct);
3108 const one_id = try self.constInt(result_ty, 1, .direct);
3109
3110 return try self.buildSelect(
3111 value,
3112 Temporary.init(result_ty, one_id),
3113 Temporary.init(result_ty, zero_id),
3114 );
22503115 }
22513116
22523117 /// Convert representation from indirect (in memory) to direct (in 'register')
22533118 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).
22543119 fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
22553120 const mod = self.module;
2256 return switch (ty.zigTypeTag(mod)) {
2257 .Bool => blk: {
2258 const result_id = self.spv.allocId();
2259 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
2260 .id_result_type = try self.resolveType(Type.bool, .direct),
2261 .id_result = result_id,
2262 .operand_1 = operand_id,
2263 .operand_2 = try self.constBool(false, .indirect),
2264 });
2265 break :blk result_id;
3121 switch (ty.scalarType(mod).zigTypeTag(mod)) {
3122 .Bool => {
3123 const false_id = try self.constBool(false, .indirect);
3124 // The operation below requires inputs in direct representation, but the operand
3125 // is actually in indirect representation.
3126 // Cheekily swap out the type to the direct equivalent of the indirect type here, they have the
3127 // same representation when converted to SPIR-V.
3128 const operand_ty = try self.zigScalarOrVectorTypeLike(Type.u1, ty);
3129 // Note: We can guarantee that these are the same ID due to the SPIR-V Module's `vector_types` cache!
3130 assert(try self.resolveType(operand_ty, .direct) == try self.resolveType(ty, .indirect));
3131
3132 const result = try self.buildCmp(
3133 .i_ne,
3134 Temporary.init(operand_ty, operand_id),
3135 Temporary.init(Type.u1, false_id),
3136 );
3137 return try result.materialize(self);
22663138 },
2267 else => operand_id,
2268 };
3139 else => return operand_id,
3140 }
22693141 }
22703142
22713143 /// Convert representation from direct (in 'register) to direct (in memory)
22723144 /// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect).
22733145 fn convertToIndirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
22743146 const mod = self.module;
2275 return switch (ty.zigTypeTag(mod)) {
2276 .Bool => try self.intFromBool(Type.u1, operand_id),
2277 else => operand_id,
2278 };
3147 switch (ty.scalarType(mod).zigTypeTag(mod)) {
3148 .Bool => {
3149 const result = try self.intFromBool(Temporary.init(ty, operand_id));
3150 return try result.materialize(self);
3151 },
3152 else => return operand_id,
3153 }
22793154 }
22803155
22813156 fn extractField(self: *DeclGen, result_ty: Type, object: IdRef, field: u32) !IdRef {
......@@ -2292,6 +3167,21 @@ const DeclGen = struct {
22923167 return try self.convertToDirect(result_ty, result_id);
22933168 }
22943169
3170 fn extractVectorComponent(self: *DeclGen, result_ty: Type, vector_id: IdRef, field: u32) !IdRef {
3171 // Whether this is an OpTypeVector or OpTypeArray, we need to emit the same instruction regardless.
3172 const result_ty_id = try self.resolveType(result_ty, .direct);
3173 const result_id = self.spv.allocId();
3174 const indexes = [_]u32{field};
3175 try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
3176 .id_result_type = result_ty_id,
3177 .id_result = result_id,
3178 .composite = vector_id,
3179 .indexes = &indexes,
3180 });
3181 // Vector components are already stored in direct representation.
3182 return result_id;
3183 }
3184
22953185 const MemoryOptions = struct {
22963186 is_volatile: bool = false,
22973187 };
......@@ -2338,26 +3228,35 @@ const DeclGen = struct {
23383228 const air_tags = self.air.instructions.items(.tag);
23393229 const maybe_result_id: ?IdRef = switch (air_tags[@intFromEnum(inst)]) {
23403230 // zig fmt: off
2341 .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),
2342 .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
2343 .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
2344
2345
3231 .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .f_add, .i_add, .i_add),
3232 .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .f_sub, .i_sub, .i_sub),
3233 .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .f_mul, .i_mul, .i_mul),
3234
3235 .sqrt => try self.airUnOpSimple(inst, .sqrt),
3236 .sin => try self.airUnOpSimple(inst, .sin),
3237 .cos => try self.airUnOpSimple(inst, .cos),
3238 .tan => try self.airUnOpSimple(inst, .tan),
3239 .exp => try self.airUnOpSimple(inst, .exp),
3240 .exp2 => try self.airUnOpSimple(inst, .exp2),
3241 .log => try self.airUnOpSimple(inst, .log),
3242 .log2 => try self.airUnOpSimple(inst, .log2),
3243 .log10 => try self.airUnOpSimple(inst, .log10),
23463244 .abs => try self.airAbs(inst),
2347 .floor => try self.airFloor(inst),
3245 .floor => try self.airUnOpSimple(inst, .floor),
3246 .ceil => try self.airUnOpSimple(inst, .ceil),
3247 .round => try self.airUnOpSimple(inst, .round),
3248 .trunc_float => try self.airUnOpSimple(inst, .trunc),
3249 .neg, .neg_optimized => try self.airUnOpSimple(inst, .f_neg),
23483250
2349 .div_floor => try self.airDivFloor(inst),
3251 .div_float, .div_float_optimized => try self.airArithOp(inst, .f_div, .s_div, .u_div),
3252 .div_floor, .div_floor_optimized => try self.airDivFloor(inst),
3253 .div_trunc, .div_trunc_optimized => try self.airDivTrunc(inst),
23503254
2351 .div_float,
2352 .div_float_optimized,
2353 .div_trunc,
2354 .div_trunc_optimized => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv),
2355 .rem, .rem_optimized => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem),
2356 .mod, .mod_optimized => try self.airArithOp(inst, .OpFMod, .OpSMod, .OpSMod),
3255 .rem, .rem_optimized => try self.airArithOp(inst, .f_rem, .s_rem, .u_mod),
3256 .mod, .mod_optimized => try self.airArithOp(inst, .f_mod, .s_mod, .u_mod),
23573257
2358
2359 .add_with_overflow => try self.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan),
2360 .sub_with_overflow => try self.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan),
3258 .add_with_overflow => try self.airAddSubOverflow(inst, .i_add, .u_lt, .s_lt),
3259 .sub_with_overflow => try self.airAddSubOverflow(inst, .i_sub, .u_gt, .s_gt),
23613260 .mul_with_overflow => try self.airMulOverflow(inst),
23623261 .shl_with_overflow => try self.airShlOverflow(inst),
23633262
......@@ -2366,6 +3265,8 @@ const DeclGen = struct {
23663265 .ctz => try self.airClzCtz(inst, .ctz),
23673266 .clz => try self.airClzCtz(inst, .clz),
23683267
3268 .select => try self.airSelect(inst),
3269
23693270 .splat => try self.airSplat(inst),
23703271 .reduce, .reduce_optimized => try self.airReduce(inst),
23713272 .shuffle => try self.airShuffle(inst),
......@@ -2373,17 +3274,17 @@ const DeclGen = struct {
23733274 .ptr_add => try self.airPtrAdd(inst),
23743275 .ptr_sub => try self.airPtrSub(inst),
23753276
2376 .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),
2377 .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),
2378 .xor => try self.airBinOpSimple(inst, .OpBitwiseXor),
2379 .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd),
2380 .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr),
3277 .bit_and => try self.airBinOpSimple(inst, .bit_and),
3278 .bit_or => try self.airBinOpSimple(inst, .bit_or),
3279 .xor => try self.airBinOpSimple(inst, .bit_xor),
3280 .bool_and => try self.airBinOpSimple(inst, .l_and),
3281 .bool_or => try self.airBinOpSimple(inst, .l_or),
23813282
2382 .shl, .shl_exact => try self.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical),
2383 .shr, .shr_exact => try self.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic),
3283 .shl, .shl_exact => try self.airShift(inst, .sll, .sll),
3284 .shr, .shr_exact => try self.airShift(inst, .srl, .sra),
23843285
2385 .min => try self.airMinMax(inst, .lt),
2386 .max => try self.airMinMax(inst, .gt),
3286 .min => try self.airMinMax(inst, .min),
3287 .max => try self.airMinMax(inst, .max),
23873288
23883289 .bitcast => try self.airBitCast(inst),
23893290 .intcast, .trunc => try self.airIntCast(inst),
......@@ -2484,39 +3385,23 @@ const DeclGen = struct {
24843385 try self.inst_results.putNoClobber(self.gpa, inst, result_id);
24853386 }
24863387
2487 fn binOpSimple(self: *DeclGen, ty: Type, lhs_id: IdRef, rhs_id: IdRef, comptime opcode: Opcode) !IdRef {
2488 var wip = try self.elementWise(ty, false);
2489 defer wip.deinit();
2490 for (0..wip.results.len) |i| {
2491 try self.func.body.emit(self.spv.gpa, opcode, .{
2492 .id_result_type = wip.ty_id,
2493 .id_result = wip.allocId(i),
2494 .operand_1 = try wip.elementAt(ty, lhs_id, i),
2495 .operand_2 = try wip.elementAt(ty, rhs_id, i),
2496 });
2497 }
2498 return try wip.finalize();
2499 }
2500
2501 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef {
3388 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, op: BinaryOp) !?IdRef {
25023389 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2503 const lhs_id = try self.resolve(bin_op.lhs);
2504 const rhs_id = try self.resolve(bin_op.rhs);
2505 const ty = self.typeOf(bin_op.lhs);
3390 const lhs = try self.temporary(bin_op.lhs);
3391 const rhs = try self.temporary(bin_op.rhs);
25063392
2507 return try self.binOpSimple(ty, lhs_id, rhs_id, opcode);
3393 const result = try self.buildBinary(op, lhs, rhs);
3394 return try result.materialize(self);
25083395 }
25093396
2510 fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime unsigned: Opcode, comptime signed: Opcode) !?IdRef {
3397 fn airShift(self: *DeclGen, inst: Air.Inst.Index, unsigned: BinaryOp, signed: BinaryOp) !?IdRef {
25113398 const mod = self.module;
25123399 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2513 const lhs_id = try self.resolve(bin_op.lhs);
2514 const rhs_id = try self.resolve(bin_op.rhs);
3400
3401 const base = try self.temporary(bin_op.lhs);
3402 const shift = try self.temporary(bin_op.rhs);
25153403
25163404 const result_ty = self.typeOfIndex(inst);
2517 const shift_ty = self.typeOf(bin_op.rhs);
2518 const scalar_result_ty_id = try self.resolveType(result_ty.scalarType(mod), .direct);
2519 const scalar_shift_ty_id = try self.resolveType(shift_ty.scalarType(mod), .direct);
25203405
25213406 const info = self.arithmeticTypeInfo(result_ty);
25223407 switch (info.class) {
......@@ -2525,121 +3410,58 @@ const DeclGen = struct {
25253410 .float, .bool => unreachable,
25263411 }
25273412
2528 var wip = try self.elementWise(result_ty, false);
2529 defer wip.deinit();
2530 for (wip.results, 0..) |*result_id, i| {
2531 const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i);
2532 const rhs_elem_id = try wip.elementAt(shift_ty, rhs_id, i);
2533
2534 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
2535 // so just manually upcast it if required.
2536 const shift_id = if (scalar_shift_ty_id != scalar_result_ty_id) blk: {
2537 const shift_id = self.spv.allocId();
2538 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
2539 .id_result_type = wip.ty_id,
2540 .id_result = shift_id,
2541 .unsigned_value = rhs_elem_id,
2542 });
2543 break :blk shift_id;
2544 } else rhs_elem_id;
2545
2546 const value_id = self.spv.allocId();
2547 const args = .{
2548 .id_result_type = wip.ty_id,
2549 .id_result = value_id,
2550 .base = lhs_elem_id,
2551 .shift = shift_id,
2552 };
3413 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3414 // so just manually upcast it if required.
25533415
2554 if (result_ty.isSignedInt(mod)) {
2555 try self.func.body.emit(self.spv.gpa, signed, args);
2556 } else {
2557 try self.func.body.emit(self.spv.gpa, unsigned, args);
2558 }
3416 // Note: The sign may differ here between the shift and the base type, in case
3417 // of an arithmetic right shift. SPIR-V still expects the same type,
3418 // so in that case we have to cast convert to signed.
3419 const casted_shift = try self.buildIntConvert(base.ty.scalarType(mod), shift);
25593420
2560 result_id.* = try self.normalize(wip.ty, value_id, info);
2561 }
2562 return try wip.finalize();
3421 const shifted = switch (info.signedness) {
3422 .unsigned => try self.buildBinary(unsigned, base, casted_shift),
3423 .signed => try self.buildBinary(signed, base, casted_shift),
3424 };
3425
3426 const result = try self.normalize(shifted, info);
3427 return try result.materialize(self);
25633428 }
25643429
2565 fn airMinMax(self: *DeclGen, inst: Air.Inst.Index, op: std.math.CompareOperator) !?IdRef {
3430 const MinMax = enum { min, max };
3431
3432 fn airMinMax(self: *DeclGen, inst: Air.Inst.Index, op: MinMax) !?IdRef {
25663433 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2567 const lhs_id = try self.resolve(bin_op.lhs);
2568 const rhs_id = try self.resolve(bin_op.rhs);
2569 const result_ty = self.typeOfIndex(inst);
25703434
2571 return try self.minMax(result_ty, op, lhs_id, rhs_id);
3435 const lhs = try self.temporary(bin_op.lhs);
3436 const rhs = try self.temporary(bin_op.rhs);
3437
3438 const result = try self.minMax(lhs, rhs, op);
3439 return try result.materialize(self);
25723440 }
25733441
2574 fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef {
2575 const info = self.arithmeticTypeInfo(result_ty);
2576 const target = self.getTarget();
3442 fn minMax(self: *DeclGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary {
3443 const info = self.arithmeticTypeInfo(lhs.ty);
25773444
2578 const use_backup_codegen = target.os.tag == .opencl and info.class != .float;
2579 var wip = try self.elementWise(result_ty, use_backup_codegen);
2580 defer wip.deinit();
2581
2582 for (wip.results, 0..) |*result_id, i| {
2583 const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i);
2584 const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i);
2585
2586 if (use_backup_codegen) {
2587 const cmp_id = try self.cmp(op, Type.bool, wip.ty, lhs_elem_id, rhs_elem_id);
2588 result_id.* = self.spv.allocId();
2589 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2590 .id_result_type = wip.ty_id,
2591 .id_result = result_id.*,
2592 .condition = cmp_id,
2593 .object_1 = lhs_elem_id,
2594 .object_2 = rhs_elem_id,
2595 });
2596 } else {
2597 const ext_inst: Word = switch (target.os.tag) {
2598 .opencl => switch (op) {
2599 .lt => 28, // fmin
2600 .gt => 27, // fmax
2601 else => unreachable,
2602 },
2603 .vulkan => switch (info.class) {
2604 .float => switch (op) {
2605 .lt => 37, // FMin
2606 .gt => 40, // FMax
2607 else => unreachable,
2608 },
2609 .integer, .strange_integer => switch (info.signedness) {
2610 .signed => switch (op) {
2611 .lt => 39, // SMin
2612 .gt => 42, // SMax
2613 else => unreachable,
2614 },
2615 .unsigned => switch (op) {
2616 .lt => 38, // UMin
2617 .gt => 41, // UMax
2618 else => unreachable,
2619 },
2620 },
2621 .composite_integer => unreachable, // TODO
2622 .bool => unreachable,
2623 },
2624 else => unreachable,
2625 };
2626 const set_id = switch (target.os.tag) {
2627 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
2628 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),
2629 else => unreachable,
2630 };
3445 const binop: BinaryOp = switch (info.class) {
3446 .float => switch (op) {
3447 .min => .f_min,
3448 .max => .f_max,
3449 },
3450 .integer, .strange_integer => switch (info.signedness) {
3451 .signed => switch (op) {
3452 .min => .s_min,
3453 .max => .s_max,
3454 },
3455 .unsigned => switch (op) {
3456 .min => .u_min,
3457 .max => .u_max,
3458 },
3459 },
3460 .composite_integer => unreachable, // TODO
3461 .bool => unreachable,
3462 };
26313463
2632 result_id.* = self.spv.allocId();
2633 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2634 .id_result_type = wip.ty_id,
2635 .id_result = result_id.*,
2636 .set = set_id,
2637 .instruction = .{ .inst = ext_inst },
2638 .id_ref_4 = &.{ lhs_elem_id, rhs_elem_id },
2639 });
2640 }
2641 }
2642 return wip.finalize();
3464 return try self.buildBinary(binop, lhs, rhs);
26433465 }
26443466
26453467 /// This function normalizes values to a canonical representation
......@@ -2650,41 +3472,24 @@ const DeclGen = struct {
26503472 /// - Signed integers are also sign extended if they are negative.
26513473 /// All other values are returned unmodified (this makes strange integer
26523474 /// wrapping easier to use in generic operations).
2653 fn normalize(self: *DeclGen, ty: Type, value_id: IdRef, info: ArithmeticTypeInfo) !IdRef {
3475 fn normalize(self: *DeclGen, value: Temporary, info: ArithmeticTypeInfo) !Temporary {
3476 const mod = self.module;
3477 const ty = value.ty;
26543478 switch (info.class) {
2655 .integer, .bool, .float => return value_id,
3479 .integer, .bool, .float => return value,
26563480 .composite_integer => unreachable, // TODO
26573481 .strange_integer => switch (info.signedness) {
26583482 .unsigned => {
26593483 const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1;
2660 const result_id = self.spv.allocId();
2661 const mask_id = try self.constInt(ty, mask_value, .direct);
2662 try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{
2663 .id_result_type = try self.resolveType(ty, .direct),
2664 .id_result = result_id,
2665 .operand_1 = value_id,
2666 .operand_2 = mask_id,
2667 });
2668 return result_id;
3484 const mask_id = try self.constInt(ty.scalarType(mod), mask_value, .direct);
3485 return try self.buildBinary(.bit_and, value, Temporary.init(ty.scalarType(mod), mask_id));
26693486 },
26703487 .signed => {
26713488 // Shift left and right so that we can copy the sight bit that way.
2672 const shift_amt_id = try self.constInt(ty, info.backing_bits - info.bits, .direct);
2673 const left_id = self.spv.allocId();
2674 try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{
2675 .id_result_type = try self.resolveType(ty, .direct),
2676 .id_result = left_id,
2677 .base = value_id,
2678 .shift = shift_amt_id,
2679 });
2680 const right_id = self.spv.allocId();
2681 try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{
2682 .id_result_type = try self.resolveType(ty, .direct),
2683 .id_result = right_id,
2684 .base = left_id,
2685 .shift = shift_amt_id,
2686 });
2687 return right_id;
3489 const shift_amt_id = try self.constInt(ty.scalarType(mod), info.backing_bits - info.bits, .direct);
3490 const shift_amt = Temporary.init(ty.scalarType(mod), shift_amt_id);
3491 const left = try self.buildBinary(.sll, value, shift_amt);
3492 return try self.buildBinary(.sra, left, shift_amt);
26883493 },
26893494 },
26903495 }
......@@ -2692,491 +3497,438 @@ const DeclGen = struct {
26923497
26933498 fn airDivFloor(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
26943499 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2695 const lhs_id = try self.resolve(bin_op.lhs);
2696 const rhs_id = try self.resolve(bin_op.rhs);
2697 const ty = self.typeOfIndex(inst);
2698 const ty_id = try self.resolveType(ty, .direct);
2699 const info = self.arithmeticTypeInfo(ty);
3500
3501 const lhs = try self.temporary(bin_op.lhs);
3502 const rhs = try self.temporary(bin_op.rhs);
3503
3504 const info = self.arithmeticTypeInfo(lhs.ty);
27003505 switch (info.class) {
27013506 .composite_integer => unreachable, // TODO
27023507 .integer, .strange_integer => {
2703 const zero_id = try self.constInt(ty, 0, .direct);
2704 const one_id = try self.constInt(ty, 1, .direct);
2705
2706 // (a ^ b) > 0
2707 const bin_bitwise_id = try self.binOpSimple(ty, lhs_id, rhs_id, .OpBitwiseXor);
2708 const is_positive_id = try self.cmp(.gt, Type.bool, ty, bin_bitwise_id, zero_id);
2709
2710 // a / b
2711 const positive_div_id = try self.arithOp(ty, lhs_id, rhs_id, .OpFDiv, .OpSDiv, .OpUDiv);
2712
2713 // - (abs(a) + abs(b) - 1) / abs(b)
2714 const lhs_abs = try self.abs(ty, ty, lhs_id);
2715 const rhs_abs = try self.abs(ty, ty, rhs_id);
2716 const negative_div_lhs = try self.arithOp(
2717 ty,
2718 try self.arithOp(ty, lhs_abs, rhs_abs, .OpFAdd, .OpIAdd, .OpIAdd),
2719 one_id,
2720 .OpFSub,
2721 .OpISub,
2722 .OpISub,
3508 switch (info.signedness) {
3509 .unsigned => {
3510 const result = try self.buildBinary(.u_div, lhs, rhs);
3511 return try result.materialize(self);
3512 },
3513 .signed => {},
3514 }
3515
3516 // For signed integers:
3517 // (a / b) - (a % b != 0 && a < 0 != b < 0);
3518 // There shouldn't be any overflow issues.
3519
3520 const div = try self.buildBinary(.s_div, lhs, rhs);
3521 const rem = try self.buildBinary(.s_rem, lhs, rhs);
3522
3523 const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0, .direct));
3524
3525 const rem_is_not_zero = try self.buildCmp(.i_ne, rem, zero);
3526
3527 const result_negative = try self.buildCmp(
3528 .l_ne,
3529 try self.buildCmp(.s_lt, lhs, zero),
3530 try self.buildCmp(.s_lt, rhs, zero),
3531 );
3532 const rem_is_not_zero_and_result_is_negative = try self.buildBinary(
3533 .l_and,
3534 rem_is_not_zero,
3535 result_negative,
27233536 );
2724 const negative_div_id = try self.arithOp(ty, negative_div_lhs, rhs_abs, .OpFDiv, .OpSDiv, .OpUDiv);
2725 const negated_negative_div_id = self.spv.allocId();
2726 try self.func.body.emit(self.spv.gpa, .OpSNegate, .{
2727 .id_result_type = ty_id,
2728 .id_result = negated_negative_div_id,
2729 .operand = negative_div_id,
2730 });
27313537
2732 const result_id = self.spv.allocId();
2733 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2734 .id_result_type = ty_id,
2735 .id_result = result_id,
2736 .condition = is_positive_id,
2737 .object_1 = positive_div_id,
2738 .object_2 = negated_negative_div_id,
2739 });
2740 return result_id;
3538 const result = try self.buildBinary(
3539 .i_sub,
3540 div,
3541 try self.intFromBool2(rem_is_not_zero_and_result_is_negative, div.ty),
3542 );
3543
3544 return try result.materialize(self);
27413545 },
27423546 .float => {
2743 const div_id = try self.arithOp(ty, lhs_id, rhs_id, .OpFDiv, .OpSDiv, .OpUDiv);
2744 return try self.floor(ty, div_id);
3547 const div = try self.buildBinary(.f_div, lhs, rhs);
3548 const result = try self.buildUnary(.floor, div);
3549 return try result.materialize(self);
27453550 },
27463551 .bool => unreachable,
27473552 }
27483553 }
27493554
2750 fn airFloor(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2751 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2752 const operand_id = try self.resolve(un_op);
2753 const result_ty = self.typeOfIndex(inst);
2754 return try self.floor(result_ty, operand_id);
2755 }
3555 fn airDivTrunc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3556 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
27563557
2757 fn floor(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
2758 const target = self.getTarget();
2759 const ty_id = try self.resolveType(ty, .direct);
2760 const ext_inst: Word = switch (target.os.tag) {
2761 .opencl => 25,
2762 .vulkan => 8,
2763 else => unreachable,
2764 };
2765 const set_id = switch (target.os.tag) {
2766 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
2767 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),
2768 else => unreachable,
2769 };
3558 const lhs = try self.temporary(bin_op.lhs);
3559 const rhs = try self.temporary(bin_op.rhs);
27703560
2771 const result_id = self.spv.allocId();
2772 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2773 .id_result_type = ty_id,
2774 .id_result = result_id,
2775 .set = set_id,
2776 .instruction = .{ .inst = ext_inst },
2777 .id_ref_4 = &.{operand_id},
2778 });
2779 return result_id;
3561 const info = self.arithmeticTypeInfo(lhs.ty);
3562 switch (info.class) {
3563 .composite_integer => unreachable, // TODO
3564 .integer, .strange_integer => switch (info.signedness) {
3565 .unsigned => {
3566 const result = try self.buildBinary(.u_div, lhs, rhs);
3567 return try result.materialize(self);
3568 },
3569 .signed => {
3570 const result = try self.buildBinary(.s_div, lhs, rhs);
3571 return try result.materialize(self);
3572 },
3573 },
3574 .float => {
3575 const div = try self.buildBinary(.f_div, lhs, rhs);
3576 const result = try self.buildUnary(.trunc, div);
3577 return try result.materialize(self);
3578 },
3579 .bool => unreachable,
3580 }
3581 }
3582
3583 fn airUnOpSimple(self: *DeclGen, inst: Air.Inst.Index, op: UnaryOp) !?IdRef {
3584 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
3585 const operand = try self.temporary(un_op);
3586 const result = try self.buildUnary(op, operand);
3587 return try result.materialize(self);
27803588 }
27813589
27823590 fn airArithOp(
27833591 self: *DeclGen,
27843592 inst: Air.Inst.Index,
2785 comptime fop: Opcode,
2786 comptime sop: Opcode,
2787 comptime uop: Opcode,
3593 comptime fop: BinaryOp,
3594 comptime sop: BinaryOp,
3595 comptime uop: BinaryOp,
27883596 ) !?IdRef {
2789 // LHS and RHS are guaranteed to have the same type, and AIR guarantees
2790 // the result to be the same as the LHS and RHS, which matches SPIR-V.
2791 const ty = self.typeOfIndex(inst);
27923597 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2793 const lhs_id = try self.resolve(bin_op.lhs);
2794 const rhs_id = try self.resolve(bin_op.rhs);
27953598
2796 assert(self.typeOf(bin_op.lhs).eql(ty, self.module));
2797 assert(self.typeOf(bin_op.rhs).eql(ty, self.module));
3599 const lhs = try self.temporary(bin_op.lhs);
3600 const rhs = try self.temporary(bin_op.rhs);
27983601
2799 return try self.arithOp(ty, lhs_id, rhs_id, fop, sop, uop);
2800 }
3602 const info = self.arithmeticTypeInfo(lhs.ty);
28013603
2802 fn arithOp(
2803 self: *DeclGen,
2804 ty: Type,
2805 lhs_id: IdRef,
2806 rhs_id: IdRef,
2807 comptime fop: Opcode,
2808 comptime sop: Opcode,
2809 comptime uop: Opcode,
2810 ) !IdRef {
2811 // Binary operations are generally applicable to both scalar and vector operations
2812 // in SPIR-V, but int and float versions of operations require different opcodes.
2813 const info = self.arithmeticTypeInfo(ty);
2814
2815 const opcode_index: usize = switch (info.class) {
2816 .composite_integer => {
2817 return self.todo("binary operations for composite integers", .{});
2818 },
3604 const result = switch (info.class) {
3605 .composite_integer => unreachable, // TODO
28193606 .integer, .strange_integer => switch (info.signedness) {
2820 .signed => 1,
2821 .unsigned => 2,
3607 .signed => try self.buildBinary(sop, lhs, rhs),
3608 .unsigned => try self.buildBinary(uop, lhs, rhs),
28223609 },
2823 .float => 0,
3610 .float => try self.buildBinary(fop, lhs, rhs),
28243611 .bool => unreachable,
28253612 };
28263613
2827 var wip = try self.elementWise(ty, false);
2828 defer wip.deinit();
2829 for (wip.results, 0..) |*result_id, i| {
2830 const lhs_elem_id = try wip.elementAt(ty, lhs_id, i);
2831 const rhs_elem_id = try wip.elementAt(ty, rhs_id, i);
2832
2833 const value_id = self.spv.allocId();
2834 const operands = .{
2835 .id_result_type = wip.ty_id,
2836 .id_result = value_id,
2837 .operand_1 = lhs_elem_id,
2838 .operand_2 = rhs_elem_id,
2839 };
2840
2841 switch (opcode_index) {
2842 0 => try self.func.body.emit(self.spv.gpa, fop, operands),
2843 1 => try self.func.body.emit(self.spv.gpa, sop, operands),
2844 2 => try self.func.body.emit(self.spv.gpa, uop, operands),
2845 else => unreachable,
2846 }
2847
2848 // TODO: Trap on overflow? Probably going to be annoying.
2849 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
2850 result_id.* = try self.normalize(wip.ty, value_id, info);
2851 }
2852
2853 return try wip.finalize();
3614 return try result.materialize(self);
28543615 }
28553616
28563617 fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
28573618 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2858 const operand_id = try self.resolve(ty_op.operand);
3619 const operand = try self.temporary(ty_op.operand);
28593620 // Note: operand_ty may be signed, while ty is always unsigned!
2860 const operand_ty = self.typeOf(ty_op.operand);
28613621 const result_ty = self.typeOfIndex(inst);
2862 return try self.abs(result_ty, operand_ty, operand_id);
3622 const result = try self.abs(result_ty, operand);
3623 return try result.materialize(self);
28633624 }
28643625
2865 fn abs(self: *DeclGen, result_ty: Type, operand_ty: Type, operand_id: IdRef) !IdRef {
3626 fn abs(self: *DeclGen, result_ty: Type, value: Temporary) !Temporary {
28663627 const target = self.getTarget();
2867 const operand_info = self.arithmeticTypeInfo(operand_ty);
2868
2869 var wip = try self.elementWise(result_ty, false);
2870 defer wip.deinit();
3628 const operand_info = self.arithmeticTypeInfo(value.ty);
28713629
2872 for (wip.results, 0..) |*result_id, i| {
2873 const elem_id = try wip.elementAt(operand_ty, operand_id, i);
2874
2875 const ext_inst: Word = switch (target.os.tag) {
2876 .opencl => switch (operand_info.class) {
2877 .float => 23, // fabs
2878 .integer, .strange_integer => switch (operand_info.signedness) {
2879 .signed => 141, // s_abs
2880 .unsigned => 201, // u_abs
2881 },
2882 .composite_integer => unreachable, // TODO
2883 .bool => unreachable,
2884 },
2885 .vulkan => switch (operand_info.class) {
2886 .float => 4, // FAbs
2887 .integer, .strange_integer => 5, // SAbs
2888 .composite_integer => unreachable, // TODO
2889 .bool => unreachable,
2890 },
2891 else => unreachable,
2892 };
2893 const set_id = switch (target.os.tag) {
2894 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
2895 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),
2896 else => unreachable,
2897 };
3630 switch (operand_info.class) {
3631 .float => return try self.buildUnary(.f_abs, value),
3632 .integer, .strange_integer => {
3633 const abs_value = try self.buildUnary(.i_abs, value);
28983634
2899 result_id.* = self.spv.allocId();
2900 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2901 .id_result_type = wip.ty_id,
2902 .id_result = result_id.*,
2903 .set = set_id,
2904 .instruction = .{ .inst = ext_inst },
2905 .id_ref_4 = &.{elem_id},
2906 });
3635 // TODO: We may need to bitcast the result to a uint
3636 // depending on the result type. Do that when
3637 // bitCast is implemented for vectors.
3638 // This is only relevant for Vulkan
3639 assert(target.os.tag != .vulkan); // TODO
3640
3641 return try self.normalize(abs_value, self.arithmeticTypeInfo(result_ty));
3642 },
3643 .composite_integer => unreachable, // TODO
3644 .bool => unreachable,
29073645 }
2908 return try wip.finalize();
29093646 }
29103647
29113648 fn airAddSubOverflow(
29123649 self: *DeclGen,
29133650 inst: Air.Inst.Index,
2914 comptime add: Opcode,
2915 comptime ucmp: Opcode,
2916 comptime scmp: Opcode,
3651 comptime add: BinaryOp,
3652 comptime ucmp: CmpPredicate,
3653 comptime scmp: CmpPredicate,
29173654 ) !?IdRef {
2918 const mod = self.module;
3655 // Note: OpIAddCarry and OpISubBorrow are not really useful here: For unsigned numbers,
3656 // there is in both cases only one extra operation required. For signed operations,
3657 // the overflow bit is set then going from 0x80.. to 0x00.., but this doesn't actually
3658 // normally set a carry bit. So the SPIR-V overflow operations are not particularly
3659 // useful here.
3660
29193661 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
29203662 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
2921 const lhs = try self.resolve(extra.lhs);
2922 const rhs = try self.resolve(extra.rhs);
29233663
2924 const result_ty = self.typeOfIndex(inst);
2925 const operand_ty = self.typeOf(extra.lhs);
2926 const ov_ty = result_ty.structFieldType(1, self.module);
3664 const lhs = try self.temporary(extra.lhs);
3665 const rhs = try self.temporary(extra.rhs);
29273666
2928 const bool_ty_id = try self.resolveType(Type.bool, .direct);
2929 const cmp_ty_id = if (self.isVector(operand_ty))
2930 // TODO: Resolving a vector type with .direct should return a SPIR-V vector
2931 try self.spv.vectorType(operand_ty.vectorLen(mod), try self.resolveType(Type.bool, .direct))
2932 else
2933 bool_ty_id;
3667 const result_ty = self.typeOfIndex(inst);
29343668
2935 const info = self.arithmeticTypeInfo(operand_ty);
3669 const info = self.arithmeticTypeInfo(lhs.ty);
29363670 switch (info.class) {
2937 .composite_integer => return self.todo("overflow ops for composite integers", .{}),
3671 .composite_integer => unreachable, // TODO
29383672 .strange_integer, .integer => {},
29393673 .float, .bool => unreachable,
29403674 }
29413675
2942 var wip_result = try self.elementWise(operand_ty, false);
2943 defer wip_result.deinit();
2944 var wip_ov = try self.elementWise(ov_ty, false);
2945 defer wip_ov.deinit();
2946 for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| {
2947 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);
2948 const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i);
2949
2950 // Normalize both so that we can properly check for overflow
2951 const value_id = self.spv.allocId();
2952
2953 try self.func.body.emit(self.spv.gpa, add, .{
2954 .id_result_type = wip_result.ty_id,
2955 .id_result = value_id,
2956 .operand_1 = lhs_elem_id,
2957 .operand_2 = rhs_elem_id,
2958 });
2959
2960 // Normalize the result so that the comparisons go well
2961 result_id.* = try self.normalize(wip_result.ty, value_id, info);
2962
2963 const overflowed_id = switch (info.signedness) {
2964 .unsigned => blk: {
2965 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
2966 // For subtraction the conditions need to be swapped.
2967 const overflowed_id = self.spv.allocId();
2968 try self.func.body.emit(self.spv.gpa, ucmp, .{
2969 .id_result_type = cmp_ty_id,
2970 .id_result = overflowed_id,
2971 .operand_1 = result_id.*,
2972 .operand_2 = lhs_elem_id,
2973 });
2974 break :blk overflowed_id;
2975 },
2976 .signed => blk: {
2977 // lhs - rhs
2978 // For addition, overflow happened if:
2979 // - rhs is negative and value > lhs
2980 // - rhs is positive and value < lhs
2981 // This can be shortened to:
2982 // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs)
2983 // = (rhs < 0) == (value > lhs)
2984 // = (rhs < 0) == (lhs < value)
2985 // Note that signed overflow is also wrapping in spir-v.
2986 // For subtraction, overflow happened if:
2987 // - rhs is negative and value < lhs
2988 // - rhs is positive and value > lhs
2989 // This can be shortened to:
2990 // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs)
2991 // = (rhs < 0) == (value < lhs)
2992 // = (rhs < 0) == (lhs > value)
2993
2994 const rhs_lt_zero_id = self.spv.allocId();
2995 const zero_id = try self.constInt(wip_result.ty, 0, .direct);
2996 try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{
2997 .id_result_type = cmp_ty_id,
2998 .id_result = rhs_lt_zero_id,
2999 .operand_1 = rhs_elem_id,
3000 .operand_2 = zero_id,
3001 });
3002
3003 const value_gt_lhs_id = self.spv.allocId();
3004 try self.func.body.emit(self.spv.gpa, scmp, .{
3005 .id_result_type = cmp_ty_id,
3006 .id_result = value_gt_lhs_id,
3007 .operand_1 = lhs_elem_id,
3008 .operand_2 = result_id.*,
3009 });
3010
3011 const overflowed_id = self.spv.allocId();
3012 try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{
3013 .id_result_type = cmp_ty_id,
3014 .id_result = overflowed_id,
3015 .operand_1 = rhs_lt_zero_id,
3016 .operand_2 = value_gt_lhs_id,
3017 });
3018 break :blk overflowed_id;
3019 },
3020 };
3676 const sum = try self.buildBinary(add, lhs, rhs);
3677 const result = try self.normalize(sum, info);
3678
3679 const overflowed = switch (info.signedness) {
3680 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
3681 // For subtraction the conditions need to be swapped.
3682 .unsigned => try self.buildCmp(ucmp, result, lhs),
3683 // For addition, overflow happened if:
3684 // - rhs is negative and value > lhs
3685 // - rhs is positive and value < lhs
3686 // This can be shortened to:
3687 // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs)
3688 // = (rhs < 0) == (value > lhs)
3689 // = (rhs < 0) == (lhs < value)
3690 // Note that signed overflow is also wrapping in spir-v.
3691 // For subtraction, overflow happened if:
3692 // - rhs is negative and value < lhs
3693 // - rhs is positive and value > lhs
3694 // This can be shortened to:
3695 // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs)
3696 // = (rhs < 0) == (value < lhs)
3697 // = (rhs < 0) == (lhs > value)
3698 .signed => blk: {
3699 const zero = Temporary.init(rhs.ty, try self.constInt(rhs.ty, 0, .direct));
3700 const rhs_lt_zero = try self.buildCmp(.s_lt, rhs, zero);
3701 const result_gt_lhs = try self.buildCmp(scmp, lhs, result);
3702 break :blk try self.buildCmp(.l_eq, rhs_lt_zero, result_gt_lhs);
3703 },
3704 };
30213705
3022 ov_id.* = try self.intFromBool(wip_ov.ty, overflowed_id);
3023 }
3706 const ov = try self.intFromBool(overflowed);
30243707
30253708 return try self.constructStruct(
30263709 result_ty,
3027 &.{ operand_ty, ov_ty },
3028 &.{ try wip_result.finalize(), try wip_ov.finalize() },
3710 &.{ result.ty, ov.ty },
3711 &.{ try result.materialize(self), try ov.materialize(self) },
30293712 );
30303713 }
30313714
30323715 fn airMulOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3716 const target = self.getTarget();
3717 const mod = self.module;
3718
30333719 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
30343720 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3035 const lhs = try self.resolve(extra.lhs);
3036 const rhs = try self.resolve(extra.rhs);
3721
3722 const lhs = try self.temporary(extra.lhs);
3723 const rhs = try self.temporary(extra.rhs);
30373724
30383725 const result_ty = self.typeOfIndex(inst);
3039 const operand_ty = self.typeOf(extra.lhs);
3040 const ov_ty = result_ty.structFieldType(1, self.module);
30413726
3042 const info = self.arithmeticTypeInfo(operand_ty);
3727 const info = self.arithmeticTypeInfo(lhs.ty);
30433728 switch (info.class) {
3044 .composite_integer => return self.todo("overflow ops for composite integers", .{}),
3729 .composite_integer => unreachable, // TODO
30453730 .strange_integer, .integer => {},
30463731 .float, .bool => unreachable,
30473732 }
30483733
3049 var wip_result = try self.elementWise(operand_ty, true);
3050 defer wip_result.deinit();
3051 var wip_ov = try self.elementWise(ov_ty, true);
3052 defer wip_ov.deinit();
3734 // There are 3 cases which we have to deal with:
3735 // - If info.bits < 32 / 2, we will upcast to 32 and check the higher bits
3736 // - If info.bits > 32 / 2, we have to use extended multiplication
3737 // - Additionally, if info.bits != 32, we'll have to check the high bits
3738 // of the result too.
3739
3740 const largest_int_bits: u16 = if (Target.spirv.featureSetHas(target.cpu.features, .Int64)) 64 else 32;
3741 // If non-null, the number of bits that the multiplication should be performed in. If
3742 // null, we have to use wide multiplication.
3743 const maybe_op_ty_bits: ?u16 = switch (info.bits) {
3744 0 => unreachable,
3745 1...16 => 32,
3746 17...32 => if (largest_int_bits > 32) 64 else null, // Upcast if we can.
3747 33...64 => null, // Always use wide multiplication.
3748 else => unreachable, // TODO: Composite integers
3749 };
30533750
3054 const zero_id = try self.constInt(wip_result.ty, 0, .direct);
3055 const zero_ov_id = try self.constInt(wip_ov.ty, 0, .direct);
3056 const one_ov_id = try self.constInt(wip_ov.ty, 1, .direct);
3751 const result, const overflowed = switch (info.signedness) {
3752 .unsigned => blk: {
3753 if (maybe_op_ty_bits) |op_ty_bits| {
3754 const op_ty = try mod.intType(.unsigned, op_ty_bits);
3755 const casted_lhs = try self.buildIntConvert(op_ty, lhs);
3756 const casted_rhs = try self.buildIntConvert(op_ty, rhs);
30573757
3058 for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| {
3059 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);
3060 const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i);
3758 const full_result = try self.buildBinary(.i_mul, casted_lhs, casted_rhs);
30613759
3062 result_id.* = try self.arithOp(wip_result.ty, lhs_elem_id, rhs_elem_id, .OpFMul, .OpIMul, .OpIMul);
3760 const low_bits = try self.buildIntConvert(lhs.ty, full_result);
3761 const result = try self.normalize(low_bits, info);
30633762
3064 // (a != 0) and (x / a != b)
3065 const not_zero_id = try self.cmp(.neq, Type.bool, wip_result.ty, lhs_elem_id, zero_id);
3066 const res_rhs_id = try self.arithOp(wip_result.ty, result_id.*, lhs_elem_id, .OpFDiv, .OpSDiv, .OpUDiv);
3067 const res_rhs_not_rhs_id = try self.cmp(.neq, Type.bool, wip_result.ty, res_rhs_id, rhs_elem_id);
3068 const cond_id = try self.binOpSimple(Type.bool, not_zero_id, res_rhs_not_rhs_id, .OpLogicalAnd);
3763 // Shift the result bits away to get the overflow bits.
3764 const shift = Temporary.init(full_result.ty, try self.constInt(full_result.ty, info.bits, .direct));
3765 const overflow = try self.buildBinary(.srl, full_result, shift);
30693766
3070 ov_id.* = self.spv.allocId();
3071 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
3072 .id_result_type = wip_ov.ty_id,
3073 .id_result = ov_id.*,
3074 .condition = cond_id,
3075 .object_1 = one_ov_id,
3076 .object_2 = zero_ov_id,
3077 });
3078 }
3767 // Directly check if its zero in the op_ty without converting first.
3768 const zero = Temporary.init(full_result.ty, try self.constInt(full_result.ty, 0, .direct));
3769 const overflowed = try self.buildCmp(.i_ne, zero, overflow);
3770
3771 break :blk .{ result, overflowed };
3772 }
3773
3774 const low_bits, const high_bits = try self.buildWideMul(.u_mul_extended, lhs, rhs);
3775
3776 // Truncate the result, if required.
3777 const result = try self.normalize(low_bits, info);
3778
3779 // Overflow happened if the high-bits of the result are non-zero OR if the
3780 // high bits of the low word of the result (those outside the range of the
3781 // int) are nonzero.
3782 const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0, .direct));
3783 const high_overflowed = try self.buildCmp(.i_ne, zero, high_bits);
3784
3785 // If no overflow bits in low_bits, no extra work needs to be done.
3786 if (info.backing_bits == info.bits) {
3787 break :blk .{ result, high_overflowed };
3788 }
3789
3790 // Shift the result bits away to get the overflow bits.
3791 const shift = Temporary.init(lhs.ty, try self.constInt(lhs.ty, info.bits, .direct));
3792 const low_overflow = try self.buildBinary(.srl, low_bits, shift);
3793 const low_overflowed = try self.buildCmp(.i_ne, zero, low_overflow);
3794
3795 const overflowed = try self.buildBinary(.l_or, low_overflowed, high_overflowed);
3796
3797 break :blk .{ result, overflowed };
3798 },
3799 .signed => blk: {
3800 // - lhs >= 0, rhxs >= 0: expect positive; overflow should be 0
3801 // - lhs == 0 : expect positive; overflow should be 0
3802 // - rhs == 0: expect positive; overflow should be 0
3803 // - lhs > 0, rhs < 0: expect negative; overflow should be -1
3804 // - lhs < 0, rhs > 0: expect negative; overflow should be -1
3805 // - lhs <= 0, rhs <= 0: expect positive; overflow should be 0
3806 // ------
3807 // overflow should be -1 when
3808 // (lhs > 0 && rhs < 0) || (lhs < 0 && rhs > 0)
3809
3810 const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0, .direct));
3811 const lhs_negative = try self.buildCmp(.s_lt, lhs, zero);
3812 const rhs_negative = try self.buildCmp(.s_lt, rhs, zero);
3813 const lhs_positive = try self.buildCmp(.s_gt, lhs, zero);
3814 const rhs_positive = try self.buildCmp(.s_gt, rhs, zero);
3815
3816 // Set to `true` if we expect -1.
3817 const expected_overflow_bit = try self.buildBinary(
3818 .l_or,
3819 try self.buildBinary(.l_and, lhs_positive, rhs_negative),
3820 try self.buildBinary(.l_and, lhs_negative, rhs_positive),
3821 );
3822
3823 if (maybe_op_ty_bits) |op_ty_bits| {
3824 const op_ty = try mod.intType(.signed, op_ty_bits);
3825 // Assume normalized; sign bit is set. We want a sign extend.
3826 const casted_lhs = try self.buildIntConvert(op_ty, lhs);
3827 const casted_rhs = try self.buildIntConvert(op_ty, rhs);
3828
3829 const full_result = try self.buildBinary(.i_mul, casted_lhs, casted_rhs);
3830
3831 // Truncate to the result type.
3832 const low_bits = try self.buildIntConvert(lhs.ty, full_result);
3833 const result = try self.normalize(low_bits, info);
3834
3835 // Now, we need to check the overflow bits AND the sign
3836 // bit for the expceted overflow bits.
3837 // To do that, shift out everything bit the sign bit and
3838 // then check what remains.
3839 const shift = Temporary.init(full_result.ty, try self.constInt(full_result.ty, info.bits - 1, .direct));
3840 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set
3841 // for negative cases.
3842 const overflow = try self.buildBinary(.sra, full_result, shift);
3843
3844 const long_all_set = Temporary.init(full_result.ty, try self.constInt(full_result.ty, -1, .direct));
3845 const long_zero = Temporary.init(full_result.ty, try self.constInt(full_result.ty, 0, .direct));
3846 const mask = try self.buildSelect(expected_overflow_bit, long_all_set, long_zero);
3847
3848 const overflowed = try self.buildCmp(.i_ne, mask, overflow);
3849
3850 break :blk .{ result, overflowed };
3851 }
3852
3853 const low_bits, const high_bits = try self.buildWideMul(.s_mul_extended, lhs, rhs);
3854
3855 // Truncate result if required.
3856 const result = try self.normalize(low_bits, info);
3857
3858 const all_set = Temporary.init(lhs.ty, try self.constInt(lhs.ty, -1, .direct));
3859 const mask = try self.buildSelect(expected_overflow_bit, all_set, zero);
3860
3861 // Like with unsigned, overflow happened if high_bits are not the ones we expect,
3862 // and we also need to check some ones from the low bits.
3863
3864 const high_overflowed = try self.buildCmp(.i_ne, mask, high_bits);
3865
3866 // If no overflow bits in low_bits, no extra work needs to be done.
3867 // Careful, we still have to check the sign bit, so this branch
3868 // only goes for i33 and such.
3869 if (info.backing_bits == info.bits + 1) {
3870 break :blk .{ result, high_overflowed };
3871 }
3872
3873 // Shift the result bits away to get the overflow bits.
3874 const shift = Temporary.init(lhs.ty, try self.constInt(lhs.ty, info.bits - 1, .direct));
3875 // Use SRA so that any sign bits are duplicated. Now we can just check if ALL bits are set
3876 // for negative cases.
3877 const low_overflow = try self.buildBinary(.sra, low_bits, shift);
3878 const low_overflowed = try self.buildCmp(.i_ne, mask, low_overflow);
3879
3880 const overflowed = try self.buildBinary(.l_or, low_overflowed, high_overflowed);
3881
3882 break :blk .{ result, overflowed };
3883 },
3884 };
3885
3886 const ov = try self.intFromBool(overflowed);
30793887
30803888 return try self.constructStruct(
30813889 result_ty,
3082 &.{ operand_ty, ov_ty },
3083 &.{ try wip_result.finalize(), try wip_ov.finalize() },
3890 &.{ result.ty, ov.ty },
3891 &.{ try result.materialize(self), try ov.materialize(self) },
30843892 );
30853893 }
30863894
30873895 fn airShlOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
30883896 const mod = self.module;
3897
30893898 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
30903899 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3091 const lhs = try self.resolve(extra.lhs);
3092 const rhs = try self.resolve(extra.rhs);
30933900
3094 const result_ty = self.typeOfIndex(inst);
3095 const operand_ty = self.typeOf(extra.lhs);
3096 const shift_ty = self.typeOf(extra.rhs);
3097 const scalar_shift_ty_id = try self.resolveType(shift_ty.scalarType(mod), .direct);
3098 const scalar_operand_ty_id = try self.resolveType(operand_ty.scalarType(mod), .direct);
3099
3100 const ov_ty = result_ty.structFieldType(1, self.module);
3901 const base = try self.temporary(extra.lhs);
3902 const shift = try self.temporary(extra.rhs);
31013903
3102 const bool_ty_id = try self.resolveType(Type.bool, .direct);
3103 const cmp_ty_id = if (self.isVector(operand_ty))
3104 // TODO: Resolving a vector type with .direct should return a SPIR-V vector
3105 try self.spv.vectorType(operand_ty.vectorLen(mod), try self.resolveType(Type.bool, .direct))
3106 else
3107 bool_ty_id;
3904 const result_ty = self.typeOfIndex(inst);
31083905
3109 const info = self.arithmeticTypeInfo(operand_ty);
3906 const info = self.arithmeticTypeInfo(base.ty);
31103907 switch (info.class) {
3111 .composite_integer => return self.todo("overflow shift for composite integers", .{}),
3908 .composite_integer => unreachable, // TODO
31123909 .integer, .strange_integer => {},
31133910 .float, .bool => unreachable,
31143911 }
31153912
3116 var wip_result = try self.elementWise(operand_ty, false);
3117 defer wip_result.deinit();
3118 var wip_ov = try self.elementWise(ov_ty, false);
3119 defer wip_ov.deinit();
3120 for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| {
3121 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);
3122 const rhs_elem_id = try wip_result.elementAt(shift_ty, rhs, i);
3123
3124 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3125 // so just manually upcast it if required.
3126 const shift_id = if (scalar_shift_ty_id != scalar_operand_ty_id) blk: {
3127 const shift_id = self.spv.allocId();
3128 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
3129 .id_result_type = wip_result.ty_id,
3130 .id_result = shift_id,
3131 .unsigned_value = rhs_elem_id,
3132 });
3133 break :blk shift_id;
3134 } else rhs_elem_id;
3135
3136 const value_id = self.spv.allocId();
3137 try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{
3138 .id_result_type = wip_result.ty_id,
3139 .id_result = value_id,
3140 .base = lhs_elem_id,
3141 .shift = shift_id,
3142 });
3143 result_id.* = try self.normalize(wip_result.ty, value_id, info);
3913 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3914 // so just manually upcast it if required.
3915 const casted_shift = try self.buildIntConvert(base.ty.scalarType(mod), shift);
31443916
3145 const right_shift_id = self.spv.allocId();
3146 switch (info.signedness) {
3147 .signed => {
3148 try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{
3149 .id_result_type = wip_result.ty_id,
3150 .id_result = right_shift_id,
3151 .base = result_id.*,
3152 .shift = shift_id,
3153 });
3154 },
3155 .unsigned => {
3156 try self.func.body.emit(self.spv.gpa, .OpShiftRightLogical, .{
3157 .id_result_type = wip_result.ty_id,
3158 .id_result = right_shift_id,
3159 .base = result_id.*,
3160 .shift = shift_id,
3161 });
3162 },
3163 }
3917 const left = try self.buildBinary(.sll, base, casted_shift);
3918 const result = try self.normalize(left, info);
31643919
3165 const overflowed_id = self.spv.allocId();
3166 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
3167 .id_result_type = cmp_ty_id,
3168 .id_result = overflowed_id,
3169 .operand_1 = lhs_elem_id,
3170 .operand_2 = right_shift_id,
3171 });
3920 const right = switch (info.signedness) {
3921 .unsigned => try self.buildBinary(.srl, result, casted_shift),
3922 .signed => try self.buildBinary(.sra, result, casted_shift),
3923 };
31723924
3173 ov_id.* = try self.intFromBool(wip_ov.ty, overflowed_id);
3174 }
3925 const overflowed = try self.buildCmp(.i_ne, base, right);
3926 const ov = try self.intFromBool(overflowed);
31753927
31763928 return try self.constructStruct(
31773929 result_ty,
3178 &.{ operand_ty, ov_ty },
3179 &.{ try wip_result.finalize(), try wip_ov.finalize() },
3930 &.{ result.ty, ov.ty },
3931 &.{ try result.materialize(self), try ov.materialize(self) },
31803932 );
31813933 }
31823934
......@@ -3184,122 +3936,67 @@ const DeclGen = struct {
31843936 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
31853937 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
31863938
3187 const mulend1 = try self.resolve(extra.lhs);
3188 const mulend2 = try self.resolve(extra.rhs);
3189 const addend = try self.resolve(pl_op.operand);
3939 const a = try self.temporary(extra.lhs);
3940 const b = try self.temporary(extra.rhs);
3941 const c = try self.temporary(pl_op.operand);
31903942
3191 const ty = self.typeOfIndex(inst);
3192
3193 const info = self.arithmeticTypeInfo(ty);
3943 const result_ty = self.typeOfIndex(inst);
3944 const info = self.arithmeticTypeInfo(result_ty);
31943945 assert(info.class == .float); // .mul_add is only emitted for floats
31953946
3196 var wip = try self.elementWise(ty, false);
3197 defer wip.deinit();
3198 for (0..wip.results.len) |i| {
3199 const mul_result = self.spv.allocId();
3200 try self.func.body.emit(self.spv.gpa, .OpFMul, .{
3201 .id_result_type = wip.ty_id,
3202 .id_result = mul_result,
3203 .operand_1 = try wip.elementAt(ty, mulend1, i),
3204 .operand_2 = try wip.elementAt(ty, mulend2, i),
3205 });
3206
3207 try self.func.body.emit(self.spv.gpa, .OpFAdd, .{
3208 .id_result_type = wip.ty_id,
3209 .id_result = wip.allocId(i),
3210 .operand_1 = mul_result,
3211 .operand_2 = try wip.elementAt(ty, addend, i),
3212 });
3213 }
3214 return try wip.finalize();
3947 const result = try self.buildFma(a, b, c);
3948 return try result.materialize(self);
32153949 }
32163950
3217 fn airClzCtz(self: *DeclGen, inst: Air.Inst.Index, op: enum { clz, ctz }) !?IdRef {
3951 fn airClzCtz(self: *DeclGen, inst: Air.Inst.Index, op: UnaryOp) !?IdRef {
32183952 if (self.liveness.isUnused(inst)) return null;
32193953
32203954 const mod = self.module;
32213955 const target = self.getTarget();
32223956 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3223 const result_ty = self.typeOfIndex(inst);
3224 const operand_ty = self.typeOf(ty_op.operand);
3225 const operand = try self.resolve(ty_op.operand);
3957 const operand = try self.temporary(ty_op.operand);
32263958
3227 const info = self.arithmeticTypeInfo(operand_ty);
3959 const scalar_result_ty = self.typeOfIndex(inst).scalarType(mod);
3960
3961 const info = self.arithmeticTypeInfo(operand.ty);
32283962 switch (info.class) {
32293963 .composite_integer => unreachable, // TODO
32303964 .integer, .strange_integer => {},
32313965 .float, .bool => unreachable,
32323966 }
32333967
3234 var wip = try self.elementWise(result_ty, false);
3235 defer wip.deinit();
3236
3237 const elem_ty = if (wip.is_array) operand_ty.scalarType(mod) else operand_ty;
3238 const elem_ty_id = try self.resolveType(elem_ty, .direct);
3239
3240 for (wip.results, 0..) |*result_id, i| {
3241 const elem = try wip.elementAt(operand_ty, operand, i);
3242
3243 switch (target.os.tag) {
3244 .opencl => {
3245 const set = try self.spv.importInstructionSet(.@"OpenCL.std");
3246 const ext_inst: u32 = switch (op) {
3247 .clz => 151, // clz
3248 .ctz => 152, // ctz
3249 };
3968 switch (target.os.tag) {
3969 .vulkan => unreachable, // TODO
3970 else => {},
3971 }
32503972
3251 // Note: result of OpenCL ctz/clz returns operand_ty, and we want result_ty.
3252 // result_ty is always large enough to hold the result, so we might have to down
3253 // cast it.
3254 const tmp = self.spv.allocId();
3255 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
3256 .id_result_type = elem_ty_id,
3257 .id_result = tmp,
3258 .set = set,
3259 .instruction = .{ .inst = ext_inst },
3260 .id_ref_4 = &.{elem},
3261 });
3973 const count = try self.buildUnary(op, operand);
32623974
3263 // TODO: Comparison should be removed..
3264 // Its valid because SpvModule caches numeric types
3265 if (wip.ty_id == elem_ty_id) {
3266 result_id.* = tmp;
3267 continue;
3268 }
3975 // Result of OpenCL ctz/clz returns operand.ty, and we want result_ty.
3976 // result_ty is always large enough to hold the result, so we might have to down
3977 // cast it.
3978 const result = try self.buildIntConvert(scalar_result_ty, count);
3979 return try result.materialize(self);
3980 }
32693981
3270 result_id.* = self.spv.allocId();
3271 if (result_ty.scalarType(mod).isSignedInt(mod)) {
3272 assert(elem_ty.scalarType(mod).isSignedInt(mod));
3273 try self.func.body.emit(self.spv.gpa, .OpSConvert, .{
3274 .id_result_type = wip.ty_id,
3275 .id_result = result_id.*,
3276 .signed_value = tmp,
3277 });
3278 } else {
3279 assert(elem_ty.scalarType(mod).isUnsignedInt(mod));
3280 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
3281 .id_result_type = wip.ty_id,
3282 .id_result = result_id.*,
3283 .unsigned_value = tmp,
3284 });
3285 }
3286 },
3287 .vulkan => unreachable, // TODO
3288 else => unreachable,
3289 }
3290 }
3982 fn airSelect(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3983 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
3984 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
3985 const pred = try self.temporary(pl_op.operand);
3986 const a = try self.temporary(extra.lhs);
3987 const b = try self.temporary(extra.rhs);
32913988
3292 return try wip.finalize();
3989 const result = try self.buildSelect(pred, a, b);
3990 return try result.materialize(self);
32933991 }
32943992
32953993 fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
32963994 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3995
32973996 const operand_id = try self.resolve(ty_op.operand);
32983997 const result_ty = self.typeOfIndex(inst);
3299 var wip = try self.elementWise(result_ty, true);
3300 defer wip.deinit();
3301 @memset(wip.results, operand_id);
3302 return try wip.finalize();
3998
3999 return try self.constructVectorSplat(result_ty, operand_id);
33034000 }
33044001
33054002 fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -3312,23 +4009,33 @@ const DeclGen = struct {
33124009
33134010 const info = self.arithmeticTypeInfo(operand_ty);
33144011
3315 var result_id = try self.extractField(scalar_ty, operand, 0);
33164012 const len = operand_ty.vectorLen(mod);
33174013
4014 const first = try self.extractVectorComponent(scalar_ty, operand, 0);
4015
33184016 switch (reduce.operation) {
33194017 .Min, .Max => |op| {
3320 const cmp_op: std.math.CompareOperator = if (op == .Max) .gt else .lt;
4018 var result = Temporary.init(scalar_ty, first);
4019 const cmp_op: MinMax = switch (op) {
4020 .Max => .max,
4021 .Min => .min,
4022 else => unreachable,
4023 };
33214024 for (1..len) |i| {
3322 const lhs = result_id;
3323 const rhs = try self.extractField(scalar_ty, operand, @intCast(i));
3324 result_id = try self.minMax(scalar_ty, cmp_op, lhs, rhs);
4025 const lhs = result;
4026 const rhs_id = try self.extractVectorComponent(scalar_ty, operand, @intCast(i));
4027 const rhs = Temporary.init(scalar_ty, rhs_id);
4028
4029 result = try self.minMax(lhs, rhs, cmp_op);
33254030 }
33264031
3327 return result_id;
4032 return try result.materialize(self);
33284033 },
33294034 else => {},
33304035 }
33314036
4037 var result_id = first;
4038
33324039 const opcode: Opcode = switch (info.class) {
33334040 .bool => switch (reduce.operation) {
33344041 .And => .OpLogicalAnd,
......@@ -3354,7 +4061,7 @@ const DeclGen = struct {
33544061
33554062 for (1..len) |i| {
33564063 const lhs = result_id;
3357 const rhs = try self.extractField(scalar_ty, operand, @intCast(i));
4064 const rhs = try self.extractVectorComponent(scalar_ty, operand, @intCast(i));
33584065 result_id = self.spv.allocId();
33594066
33604067 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
......@@ -3375,25 +4082,72 @@ const DeclGen = struct {
33754082 const b = try self.resolve(extra.b);
33764083 const mask = Value.fromInterned(extra.mask);
33774084
3378 const ty = self.typeOfIndex(inst);
4085 // Note: number of components in the result, a, and b may differ.
4086 const result_ty = self.typeOfIndex(inst);
4087 const a_ty = self.typeOf(extra.a);
4088 const b_ty = self.typeOf(extra.b);
4089
4090 const scalar_ty = result_ty.scalarType(mod);
4091 const scalar_ty_id = try self.resolveType(scalar_ty, .direct);
4092
4093 // If all of the types are SPIR-V vectors, we can use OpVectorShuffle.
4094 if (self.isSpvVector(result_ty) and self.isSpvVector(a_ty) and self.isSpvVector(b_ty)) {
4095 // The SPIR-V shuffle instruction is similar to the Air instruction, except that the elements are
4096 // numbered consecutively instead of using negatives.
4097
4098 const components = try self.gpa.alloc(Word, result_ty.vectorLen(mod));
4099 defer self.gpa.free(components);
4100
4101 const a_len = a_ty.vectorLen(mod);
4102
4103 for (components, 0..) |*component, i| {
4104 const elem = try mask.elemValue(mod, i);
4105 if (elem.isUndef(mod)) {
4106 // This is explicitly valid for OpVectorShuffle, it indicates undefined.
4107 component.* = 0xFFFF_FFFF;
4108 continue;
4109 }
33794110
3380 var wip = try self.elementWise(ty, true);
3381 defer wip.deinit();
3382 for (wip.results, 0..) |*result_id, i| {
4111 const index = elem.toSignedInt(mod);
4112 if (index >= 0) {
4113 component.* = @intCast(index);
4114 } else {
4115 component.* = @intCast(~index + a_len);
4116 }
4117 }
4118
4119 const result_id = self.spv.allocId();
4120 try self.func.body.emit(self.spv.gpa, .OpVectorShuffle, .{
4121 .id_result_type = try self.resolveType(result_ty, .direct),
4122 .id_result = result_id,
4123 .vector_1 = a,
4124 .vector_2 = b,
4125 .components = components,
4126 });
4127 return result_id;
4128 }
4129
4130 // Fall back to manually extracting and inserting components.
4131
4132 const components = try self.gpa.alloc(IdRef, result_ty.vectorLen(mod));
4133 defer self.gpa.free(components);
4134
4135 for (components, 0..) |*id, i| {
33834136 const elem = try mask.elemValue(mod, i);
33844137 if (elem.isUndef(mod)) {
3385 result_id.* = try self.spv.constUndef(wip.ty_id);
4138 id.* = try self.spv.constUndef(scalar_ty_id);
33864139 continue;
33874140 }
33884141
33894142 const index = elem.toSignedInt(mod);
33904143 if (index >= 0) {
3391 result_id.* = try self.extractField(wip.ty, a, @intCast(index));
4144 id.* = try self.extractVectorComponent(scalar_ty, a, @intCast(index));
33924145 } else {
3393 result_id.* = try self.extractField(wip.ty, b, @intCast(~index));
4146 id.* = try self.extractVectorComponent(scalar_ty, b, @intCast(~index));
33944147 }
33954148 }
3396 return try wip.finalize();
4149
4150 return try self.constructVector(result_ty, components);
33974151 }
33984152
33994153 fn indicesToIds(self: *DeclGen, indices: []const u32) ![]IdRef {
......@@ -3512,50 +4266,66 @@ const DeclGen = struct {
35124266 fn cmp(
35134267 self: *DeclGen,
35144268 op: std.math.CompareOperator,
3515 result_ty: Type,
3516 ty: Type,
3517 lhs_id: IdRef,
3518 rhs_id: IdRef,
3519 ) !IdRef {
4269 lhs: Temporary,
4270 rhs: Temporary,
4271 ) !Temporary {
35204272 const mod = self.module;
3521 var cmp_lhs_id = lhs_id;
3522 var cmp_rhs_id = rhs_id;
3523 const bool_ty_id = try self.resolveType(Type.bool, .direct);
3524 const op_ty = switch (ty.zigTypeTag(mod)) {
3525 .Int, .Bool, .Float => ty,
3526 .Enum => ty.intTagType(mod),
3527 .ErrorSet => Type.u16,
3528 .Pointer => blk: {
4273 const scalar_ty = lhs.ty.scalarType(mod);
4274 const is_vector = lhs.ty.isVector(mod);
4275
4276 switch (scalar_ty.zigTypeTag(mod)) {
4277 .Int, .Bool, .Float => {},
4278 .Enum => {
4279 assert(!is_vector);
4280 const ty = lhs.ty.intTagType(mod);
4281 return try self.cmp(op, lhs.pun(ty), rhs.pun(ty));
4282 },
4283 .ErrorSet => {
4284 assert(!is_vector);
4285 return try self.cmp(op, lhs.pun(Type.u16), rhs.pun(Type.u16));
4286 },
4287 .Pointer => {
4288 assert(!is_vector);
35294289 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are
35304290 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using
35314291 // OpConvertPtrToU...
3532 cmp_lhs_id = self.spv.allocId();
3533 cmp_rhs_id = self.spv.allocId();
35344292
35354293 const usize_ty_id = try self.resolveType(Type.usize, .direct);
35364294
4295 const lhs_int_id = self.spv.allocId();
35374296 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
35384297 .id_result_type = usize_ty_id,
3539 .id_result = cmp_lhs_id,
3540 .pointer = lhs_id,
4298 .id_result = lhs_int_id,
4299 .pointer = try lhs.materialize(self),
35414300 });
35424301
4302 const rhs_int_id = self.spv.allocId();
35434303 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
35444304 .id_result_type = usize_ty_id,
3545 .id_result = cmp_rhs_id,
3546 .pointer = rhs_id,
4305 .id_result = rhs_int_id,
4306 .pointer = try rhs.materialize(self),
35474307 });
35484308
3549 break :blk Type.usize;
4309 const lhs_int = Temporary.init(Type.usize, lhs_int_id);
4310 const rhs_int = Temporary.init(Type.usize, rhs_int_id);
4311 return try self.cmp(op, lhs_int, rhs_int);
35504312 },
35514313 .Optional => {
4314 assert(!is_vector);
4315
4316 const ty = lhs.ty;
4317
35524318 const payload_ty = ty.optionalChild(mod);
35534319 if (ty.optionalReprIsPayload(mod)) {
35544320 assert(payload_ty.hasRuntimeBitsIgnoreComptime(mod));
35554321 assert(!payload_ty.isSlice(mod));
3556 return self.cmp(op, Type.bool, payload_ty, lhs_id, rhs_id);
4322
4323 return try self.cmp(op, lhs.pun(payload_ty), rhs.pun(payload_ty));
35574324 }
35584325
4326 const lhs_id = try lhs.materialize(self);
4327 const rhs_id = try rhs.materialize(self);
4328
35594329 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod))
35604330 try self.extractField(Type.bool, lhs_id, 1)
35614331 else
......@@ -3566,8 +4336,11 @@ const DeclGen = struct {
35664336 else
35674337 try self.convertToDirect(Type.bool, rhs_id);
35684338
4339 const lhs_valid = Temporary.init(Type.bool, lhs_valid_id);
4340 const rhs_valid = Temporary.init(Type.bool, rhs_valid_id);
4341
35694342 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
3570 return try self.cmp(op, Type.bool, Type.bool, lhs_valid_id, rhs_valid_id);
4343 return try self.cmp(op, lhs_valid, rhs_valid);
35714344 }
35724345
35734346 // a = lhs_valid
......@@ -3588,118 +4361,71 @@ const DeclGen = struct {
35884361 const lhs_pl_id = try self.extractField(payload_ty, lhs_id, 0);
35894362 const rhs_pl_id = try self.extractField(payload_ty, rhs_id, 0);
35904363
3591 switch (op) {
3592 .eq => {
3593 const valid_eq_id = try self.cmp(.eq, Type.bool, Type.bool, lhs_valid_id, rhs_valid_id);
3594 const pl_eq_id = try self.cmp(op, Type.bool, payload_ty, lhs_pl_id, rhs_pl_id);
3595 const lhs_not_valid_id = self.spv.allocId();
3596 try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{
3597 .id_result_type = bool_ty_id,
3598 .id_result = lhs_not_valid_id,
3599 .operand = lhs_valid_id,
3600 });
3601 const impl_id = self.spv.allocId();
3602 try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{
3603 .id_result_type = bool_ty_id,
3604 .id_result = impl_id,
3605 .operand_1 = lhs_not_valid_id,
3606 .operand_2 = pl_eq_id,
3607 });
3608 const result_id = self.spv.allocId();
3609 try self.func.body.emit(self.spv.gpa, .OpLogicalAnd, .{
3610 .id_result_type = bool_ty_id,
3611 .id_result = result_id,
3612 .operand_1 = valid_eq_id,
3613 .operand_2 = impl_id,
3614 });
3615 return result_id;
3616 },
3617 .neq => {
3618 const valid_neq_id = try self.cmp(.neq, Type.bool, Type.bool, lhs_valid_id, rhs_valid_id);
3619 const pl_neq_id = try self.cmp(op, Type.bool, payload_ty, lhs_pl_id, rhs_pl_id);
3620
3621 const impl_id = self.spv.allocId();
3622 try self.func.body.emit(self.spv.gpa, .OpLogicalAnd, .{
3623 .id_result_type = bool_ty_id,
3624 .id_result = impl_id,
3625 .operand_1 = lhs_valid_id,
3626 .operand_2 = pl_neq_id,
3627 });
3628 const result_id = self.spv.allocId();
3629 try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{
3630 .id_result_type = bool_ty_id,
3631 .id_result = result_id,
3632 .operand_1 = valid_neq_id,
3633 .operand_2 = impl_id,
3634 });
3635 return result_id;
3636 },
4364 const lhs_pl = Temporary.init(payload_ty, lhs_pl_id);
4365 const rhs_pl = Temporary.init(payload_ty, rhs_pl_id);
4366
4367 return switch (op) {
4368 .eq => try self.buildBinary(
4369 .l_and,
4370 try self.cmp(.eq, lhs_valid, rhs_valid),
4371 try self.buildBinary(
4372 .l_or,
4373 try self.buildUnary(.l_not, lhs_valid),
4374 try self.cmp(.eq, lhs_pl, rhs_pl),
4375 ),
4376 ),
4377 .neq => try self.buildBinary(
4378 .l_or,
4379 try self.cmp(.neq, lhs_valid, rhs_valid),
4380 try self.buildBinary(
4381 .l_and,
4382 lhs_valid,
4383 try self.cmp(.neq, lhs_pl, rhs_pl),
4384 ),
4385 ),
36374386 else => unreachable,
3638 }
3639 },
3640 .Vector => {
3641 var wip = try self.elementWise(result_ty, true);
3642 defer wip.deinit();
3643 const scalar_ty = ty.scalarType(mod);
3644 for (wip.results, 0..) |*result_id, i| {
3645 const lhs_elem_id = try wip.elementAt(ty, lhs_id, i);
3646 const rhs_elem_id = try wip.elementAt(ty, rhs_id, i);
3647 result_id.* = try self.cmp(op, Type.bool, scalar_ty, lhs_elem_id, rhs_elem_id);
3648 }
3649 return wip.finalize();
4387 };
36504388 },
36514389 else => unreachable,
3652 };
4390 }
36534391
3654 const opcode: Opcode = opcode: {
3655 const info = self.arithmeticTypeInfo(op_ty);
3656 const signedness = switch (info.class) {
3657 .composite_integer => {
3658 return self.todo("binary operations for composite integers", .{});
3659 },
3660 .float => break :opcode switch (op) {
3661 .eq => .OpFOrdEqual,
3662 .neq => .OpFUnordNotEqual,
3663 .lt => .OpFOrdLessThan,
3664 .lte => .OpFOrdLessThanEqual,
3665 .gt => .OpFOrdGreaterThan,
3666 .gte => .OpFOrdGreaterThanEqual,
3667 },
3668 .bool => break :opcode switch (op) {
3669 .eq => .OpLogicalEqual,
3670 .neq => .OpLogicalNotEqual,
3671 else => unreachable,
4392 const info = self.arithmeticTypeInfo(scalar_ty);
4393 const pred: CmpPredicate = switch (info.class) {
4394 .composite_integer => unreachable, // TODO
4395 .float => switch (op) {
4396 .eq => .f_oeq,
4397 .neq => .f_une,
4398 .lt => .f_olt,
4399 .lte => .f_ole,
4400 .gt => .f_ogt,
4401 .gte => .f_oge,
4402 },
4403 .bool => switch (op) {
4404 .eq => .l_eq,
4405 .neq => .l_ne,
4406 else => unreachable,
4407 },
4408 .integer, .strange_integer => switch (info.signedness) {
4409 .signed => switch (op) {
4410 .eq => .i_eq,
4411 .neq => .i_ne,
4412 .lt => .s_lt,
4413 .lte => .s_le,
4414 .gt => .s_gt,
4415 .gte => .s_ge,
36724416 },
3673 .integer, .strange_integer => info.signedness,
3674 };
3675
3676 break :opcode switch (signedness) {
36774417 .unsigned => switch (op) {
3678 .eq => .OpIEqual,
3679 .neq => .OpINotEqual,
3680 .lt => .OpULessThan,
3681 .lte => .OpULessThanEqual,
3682 .gt => .OpUGreaterThan,
3683 .gte => .OpUGreaterThanEqual,
3684 },
3685 .signed => switch (op) {
3686 .eq => .OpIEqual,
3687 .neq => .OpINotEqual,
3688 .lt => .OpSLessThan,
3689 .lte => .OpSLessThanEqual,
3690 .gt => .OpSGreaterThan,
3691 .gte => .OpSGreaterThanEqual,
4418 .eq => .i_eq,
4419 .neq => .i_ne,
4420 .lt => .u_lt,
4421 .lte => .u_le,
4422 .gt => .u_gt,
4423 .gte => .u_ge,
36924424 },
3693 };
4425 },
36944426 };
36954427
3696 const result_id = self.spv.allocId();
3697 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
3698 self.func.body.writeOperand(spec.IdResultType, bool_ty_id);
3699 self.func.body.writeOperand(spec.IdResult, result_id);
3700 self.func.body.writeOperand(spec.IdResultType, cmp_lhs_id);
3701 self.func.body.writeOperand(spec.IdResultType, cmp_rhs_id);
3702 return result_id;
4428 return try self.buildCmp(pred, lhs, rhs);
37034429 }
37044430
37054431 fn airCmp(
......@@ -3708,24 +4434,22 @@ const DeclGen = struct {
37084434 comptime op: std.math.CompareOperator,
37094435 ) !?IdRef {
37104436 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3711 const lhs_id = try self.resolve(bin_op.lhs);
3712 const rhs_id = try self.resolve(bin_op.rhs);
3713 const ty = self.typeOf(bin_op.lhs);
3714 const result_ty = self.typeOfIndex(inst);
4437 const lhs = try self.temporary(bin_op.lhs);
4438 const rhs = try self.temporary(bin_op.rhs);
37154439
3716 return try self.cmp(op, result_ty, ty, lhs_id, rhs_id);
4440 const result = try self.cmp(op, lhs, rhs);
4441 return try result.materialize(self);
37174442 }
37184443
37194444 fn airVectorCmp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
37204445 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
37214446 const vec_cmp = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;
3722 const lhs_id = try self.resolve(vec_cmp.lhs);
3723 const rhs_id = try self.resolve(vec_cmp.rhs);
4447 const lhs = try self.temporary(vec_cmp.lhs);
4448 const rhs = try self.temporary(vec_cmp.rhs);
37244449 const op = vec_cmp.compareOperator();
3725 const ty = self.typeOf(vec_cmp.lhs);
3726 const result_ty = self.typeOfIndex(inst);
37274450
3728 return try self.cmp(op, result_ty, ty, lhs_id, rhs_id);
4451 const result = try self.cmp(op, lhs, rhs);
4452 return try result.materialize(self);
37294453 }
37304454
37314455 /// Bitcast one type to another. Note: both types, input, output are expected in **direct** representation.
......@@ -3791,7 +4515,8 @@ const DeclGen = struct {
37914515 // should we change the representation of strange integers?
37924516 if (dst_ty.zigTypeTag(mod) == .Int) {
37934517 const info = self.arithmeticTypeInfo(dst_ty);
3794 return try self.normalize(dst_ty, result_id, info);
4518 const result = try self.normalize(Temporary.init(dst_ty, result_id), info);
4519 return try result.materialize(self);
37954520 }
37964521
37974522 return result_id;
......@@ -3807,46 +4532,28 @@ const DeclGen = struct {
38074532
38084533 fn airIntCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
38094534 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3810 const operand_id = try self.resolve(ty_op.operand);
3811 const src_ty = self.typeOf(ty_op.operand);
4535 const src = try self.temporary(ty_op.operand);
38124536 const dst_ty = self.typeOfIndex(inst);
38134537
3814 const src_info = self.arithmeticTypeInfo(src_ty);
4538 const src_info = self.arithmeticTypeInfo(src.ty);
38154539 const dst_info = self.arithmeticTypeInfo(dst_ty);
38164540
38174541 if (src_info.backing_bits == dst_info.backing_bits) {
3818 return operand_id;
4542 return try src.materialize(self);
38194543 }
38204544
3821 var wip = try self.elementWise(dst_ty, false);
3822 defer wip.deinit();
3823 for (wip.results, 0..) |*result_id, i| {
3824 const elem_id = try wip.elementAt(src_ty, operand_id, i);
3825 const value_id = self.spv.allocId();
3826 switch (dst_info.signedness) {
3827 .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{
3828 .id_result_type = wip.ty_id,
3829 .id_result = value_id,
3830 .signed_value = elem_id,
3831 }),
3832 .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
3833 .id_result_type = wip.ty_id,
3834 .id_result = value_id,
3835 .unsigned_value = elem_id,
3836 }),
3837 }
4545 const converted = try self.buildIntConvert(dst_ty, src);
38384546
3839 // Make sure to normalize the result if shrinking.
3840 // Because strange ints are sign extended in their backing
3841 // type, we don't need to normalize when growing the type. The
3842 // representation is already the same.
3843 if (dst_info.bits < src_info.bits) {
3844 result_id.* = try self.normalize(wip.ty, value_id, dst_info);
3845 } else {
3846 result_id.* = value_id;
3847 }
3848 }
3849 return try wip.finalize();
4547 // Make sure to normalize the result if shrinking.
4548 // Because strange ints are sign extended in their backing
4549 // type, we don't need to normalize when growing the type. The
4550 // representation is already the same.
4551 const result = if (dst_info.bits < src_info.bits)
4552 try self.normalize(converted, dst_info)
4553 else
4554 converted;
4555
4556 return try result.materialize(self);
38504557 }
38514558
38524559 fn intFromPtr(self: *DeclGen, operand_id: IdRef) !IdRef {
......@@ -3921,16 +4628,9 @@ const DeclGen = struct {
39214628
39224629 fn airIntFromBool(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
39234630 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
3924 const operand_id = try self.resolve(un_op);
3925 const result_ty = self.typeOfIndex(inst);
3926
3927 var wip = try self.elementWise(result_ty, false);
3928 defer wip.deinit();
3929 for (wip.results, 0..) |*result_id, i| {
3930 const elem_id = try wip.elementAt(Type.bool, operand_id, i);
3931 result_id.* = try self.intFromBool(wip.ty, elem_id);
3932 }
3933 return try wip.finalize();
4631 const operand = try self.temporary(un_op);
4632 const result = try self.intFromBool(operand);
4633 return try result.materialize(self);
39344634 }
39354635
39364636 fn airFloatCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -3950,33 +4650,21 @@ const DeclGen = struct {
39504650
39514651 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
39524652 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3953 const operand_id = try self.resolve(ty_op.operand);
4653 const operand = try self.temporary(ty_op.operand);
39544654 const result_ty = self.typeOfIndex(inst);
39554655 const info = self.arithmeticTypeInfo(result_ty);
39564656
3957 var wip = try self.elementWise(result_ty, false);
3958 defer wip.deinit();
3959
3960 for (0..wip.results.len) |i| {
3961 const args = .{
3962 .id_result_type = wip.ty_id,
3963 .id_result = wip.allocId(i),
3964 .operand = try wip.elementAt(result_ty, operand_id, i),
3965 };
3966 switch (info.class) {
3967 .bool => {
3968 try self.func.body.emit(self.spv.gpa, .OpLogicalNot, args);
3969 },
3970 .float => unreachable,
3971 .composite_integer => unreachable, // TODO
3972 .strange_integer, .integer => {
3973 // Note: strange integer bits will be masked before operations that do not hold under modulo.
3974 try self.func.body.emit(self.spv.gpa, .OpNot, args);
3975 },
3976 }
3977 }
4657 const result = switch (info.class) {
4658 .bool => try self.buildUnary(.l_not, operand),
4659 .float => unreachable,
4660 .composite_integer => unreachable, // TODO
4661 .strange_integer, .integer => blk: {
4662 const complement = try self.buildUnary(.bit_not, operand);
4663 break :blk try self.normalize(complement, info);
4664 },
4665 };
39784666
3979 return try wip.finalize();
4667 return try result.materialize(self);
39804668 }
39814669
39824670 fn airArrayToSlice(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -4086,8 +4774,7 @@ const DeclGen = struct {
40864774 defer self.gpa.free(elem_ids);
40874775
40884776 for (elements, 0..) |element, i| {
4089 const id = try self.resolve(element);
4090 elem_ids[i] = try self.convertToIndirect(result_ty.childType(mod), id);
4777 elem_ids[i] = try self.resolve(element);
40914778 }
40924779
40934780 return try self.constructVector(result_ty, elem_ids);
......@@ -4234,16 +4921,57 @@ const DeclGen = struct {
42344921 const array_id = try self.resolve(bin_op.lhs);
42354922 const index_id = try self.resolve(bin_op.rhs);
42364923
4924 if (self.isSpvVector(array_ty)) {
4925 const result_id = self.spv.allocId();
4926 try self.func.body.emit(self.spv.gpa, .OpVectorExtractDynamic, .{
4927 .id_result_type = try self.resolveType(elem_ty, .direct),
4928 .id_result = result_id,
4929 .vector = array_id,
4930 .index = index_id,
4931 });
4932 return result_id;
4933 }
4934
42374935 // SPIR-V doesn't have an array indexing function for some damn reason.
42384936 // For now, just generate a temporary and use that.
42394937 // TODO: This backend probably also should use isByRef from llvm...
42404938
4241 const elem_ptr_ty_id = try self.ptrType(elem_ty, .Function);
4939 const is_vector = array_ty.isVector(mod);
4940
4941 const elem_repr: Repr = if (is_vector) .direct else .indirect;
4942 const ptr_array_ty_id = try self.ptrType2(array_ty, .Function, .direct);
4943 const ptr_elem_ty_id = try self.ptrType2(elem_ty, .Function, elem_repr);
4944
4945 const tmp_id = self.spv.allocId();
4946 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
4947 .id_result_type = ptr_array_ty_id,
4948 .id_result = tmp_id,
4949 .storage_class = .Function,
4950 });
4951
4952 try self.func.body.emit(self.spv.gpa, .OpStore, .{
4953 .pointer = tmp_id,
4954 .object = array_id,
4955 });
4956
4957 const elem_ptr_id = try self.accessChainId(ptr_elem_ty_id, tmp_id, &.{index_id});
4958
4959 const result_id = self.spv.allocId();
4960 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
4961 .id_result_type = try self.resolveType(elem_ty, elem_repr),
4962 .id_result = result_id,
4963 .pointer = elem_ptr_id,
4964 });
4965
4966 if (is_vector) {
4967 // Result is already in direct representation
4968 return result_id;
4969 }
42424970
4243 const tmp_id = try self.alloc(array_ty, .{ .storage_class = .Function });
4244 try self.store(array_ty, tmp_id, array_id, .{});
4245 const elem_ptr_id = try self.accessChainId(elem_ptr_ty_id, tmp_id, &.{index_id});
4246 return try self.load(elem_ty, elem_ptr_id, .{});
4971 // This is an array type; the elements are stored in indirect representation.
4972 // We have to convert the type to direct.
4973
4974 return try self.convertToDirect(elem_ty, result_id);
42474975 }
42484976
42494977 fn airPtrElemVal(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -4458,7 +5186,10 @@ const DeclGen = struct {
44585186 if (field_offset == 0) break :base_ptr_int field_ptr_int;
44595187
44605188 const field_offset_id = try self.constInt(Type.usize, field_offset, .direct);
4461 break :base_ptr_int try self.binOpSimple(Type.usize, field_ptr_int, field_offset_id, .OpISub);
5189 const field_ptr_tmp = Temporary.init(Type.usize, field_ptr_int);
5190 const field_offset_tmp = Temporary.init(Type.usize, field_offset_id);
5191 const result = try self.buildBinary(.i_sub, field_ptr_tmp, field_offset_tmp);
5192 break :base_ptr_int try result.materialize(self);
44625193 };
44635194
44645195 const base_ptr = self.spv.allocId();
......@@ -5273,13 +6004,17 @@ const DeclGen = struct {
52736004 else
52746005 loaded_id;
52756006
5276 const payload_ty_id = try self.resolveType(ptr_ty, .direct);
5277 const null_id = try self.spv.constNull(payload_ty_id);
6007 const ptr_ty_id = try self.resolveType(ptr_ty, .direct);
6008 const null_id = try self.spv.constNull(ptr_ty_id);
6009 const null_tmp = Temporary.init(ptr_ty, null_id);
6010 const ptr = Temporary.init(ptr_ty, ptr_id);
6011
52786012 const op: std.math.CompareOperator = switch (pred) {
52796013 .is_null => .eq,
52806014 .is_non_null => .neq,
52816015 };
5282 return try self.cmp(op, Type.bool, ptr_ty, ptr_id, null_id);
6016 const result = try self.cmp(op, ptr, null_tmp);
6017 return try result.materialize(self);
52836018 }
52846019
52856020 const is_non_null_id = blk: {
src/codegen/spirv/Module.zig+15-7
......@@ -155,6 +155,9 @@ cache: struct {
155155 void_type: ?IdRef = null,
156156 int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, IdRef) = .{},
157157 float_types: std.AutoHashMapUnmanaged(std.builtin.Type.Float, IdRef) = .{},
158 // This cache is required so that @Vector(X, u1) in direct representation has the
159 // same ID as @Vector(X, bool) in indirect representation.
160 vector_types: std.AutoHashMapUnmanaged(struct { IdRef, u32 }, IdRef) = .{},
158161} = .{},
159162
160163/// Set of Decls, referred to by Decl.Index.
......@@ -194,6 +197,7 @@ pub fn deinit(self: *Module) void {
194197
195198 self.cache.int_types.deinit(self.gpa);
196199 self.cache.float_types.deinit(self.gpa);
200 self.cache.vector_types.deinit(self.gpa);
197201
198202 self.decls.deinit(self.gpa);
199203 self.decl_deps.deinit(self.gpa);
......@@ -474,13 +478,17 @@ pub fn floatType(self: *Module, bits: u16) !IdRef {
474478}
475479
476480pub fn vectorType(self: *Module, len: u32, child_id: IdRef) !IdRef {
477 const result_id = self.allocId();
478 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVector, .{
479 .id_result = result_id,
480 .component_type = child_id,
481 .component_count = len,
482 });
483 return result_id;
481 const entry = try self.cache.vector_types.getOrPut(self.gpa, .{ child_id, len });
482 if (!entry.found_existing) {
483 const result_id = self.allocId();
484 entry.value_ptr.* = result_id;
485 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVector, .{
486 .id_result = result_id,
487 .component_type = child_id,
488 .component_count = len,
489 });
490 }
491 return entry.value_ptr.*;
484492}
485493
486494pub fn constUndef(self: *Module, ty_id: IdRef) !IdRef {
src/link/SpirV.zig+1
......@@ -232,6 +232,7 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: std.Progress.Node)
232232 // name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
233233 // We're using : as separator, which is a reserved character.
234234
235 try error_info.append(':');
235236 try std.Uri.Component.percentEncode(
236237 error_info.writer(),
237238 name.toSlice(&mod.intern_pool),
test/behavior/array.zig+1
......@@ -768,6 +768,7 @@ test "slicing array of zero-sized values" {
768768 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
769769 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
770770 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
771 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
771772
772773 var arr: [32]u0 = undefined;
773774 for (arr[0..]) |*zero|
test/behavior/byval_arg_var.zig+1
......@@ -6,6 +6,7 @@ var result: []const u8 = "wrong";
66test "pass string literal byvalue to a generic var param" {
77 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
88 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
9 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
910
1011 start();
1112 blowUpStack(10);
test/behavior/cast.zig+1
......@@ -1378,6 +1378,7 @@ test "assignment to optional pointer result loc" {
13781378
13791379test "cast between *[N]void and []void" {
13801380 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1381 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
13811382
13821383 var a: [4]void = undefined;
13831384 const b: []void = &a;
test/behavior/enum.zig+1
......@@ -1286,6 +1286,7 @@ test "matching captures causes enum equivalence" {
12861286test "large enum field values" {
12871287 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
12881288 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1289 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
12891290
12901291 {
12911292 const E = enum(u64) { min = std.math.minInt(u64), max = std.math.maxInt(u64) };
test/behavior/error.zig+3
......@@ -997,6 +997,7 @@ test "try used in recursive function with inferred error set" {
997997 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
998998 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
999999 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1000 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
10001001
10011002 const Value = union(enum) {
10021003 values: []const @This(),
......@@ -1103,6 +1104,7 @@ test "result location initialization of error union with OPV payload" {
11031104 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
11041105 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
11051106 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1107 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
11061108
11071109 const S = struct {
11081110 x: u0,
......@@ -1125,6 +1127,7 @@ test "result location initialization of error union with OPV payload" {
11251127test "return error union with i65" {
11261128 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
11271129 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1130 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
11281131
11291132 try expect(try add(1000, 234) == 1234);
11301133}
test/behavior/floatop.zig-34
......@@ -275,7 +275,6 @@ test "@sqrt f16" {
275275 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
276276 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
277277 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
278 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
279278 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
280279 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
281280
......@@ -287,7 +286,6 @@ test "@sqrt f32/f64" {
287286 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
288287 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
289288 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
290 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
291289 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
292290 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
293291
......@@ -389,7 +387,6 @@ test "@sqrt with vectors" {
389387 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
390388 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
391389 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
392 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
393390 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
394391
395392 try testSqrtWithVectors();
......@@ -410,7 +407,6 @@ test "@sin f16" {
410407 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
411408 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
412409 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
413 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
414410 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
415411 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
416412
......@@ -422,7 +418,6 @@ test "@sin f32/f64" {
422418 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
423419 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
424420 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
425 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
426421 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
427422 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
428423
......@@ -464,7 +459,6 @@ test "@sin with vectors" {
464459 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
465460 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
466461 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
467 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
468462 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
469463 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
470464
......@@ -486,7 +480,6 @@ test "@cos f16" {
486480 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
487481 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
488482 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
489 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
490483 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
491484 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
492485
......@@ -498,7 +491,6 @@ test "@cos f32/f64" {
498491 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
499492 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
500493 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
501 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
502494 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
503495 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
504496
......@@ -540,7 +532,6 @@ test "@cos with vectors" {
540532 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
541533 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
542534 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
543 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
544535 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
545536 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
546537
......@@ -574,7 +565,6 @@ test "@tan f32/f64" {
574565 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
575566 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
576567 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
577 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
578568 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
579569 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
580570
......@@ -616,7 +606,6 @@ test "@tan with vectors" {
616606 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
617607 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
618608 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
619 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
620609 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
621610 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
622611
......@@ -638,7 +627,6 @@ test "@exp f16" {
638627 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
639628 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
640629 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
641 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
642630 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
643631 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
644632
......@@ -650,7 +638,6 @@ test "@exp f32/f64" {
650638 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
651639 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
652640 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
653 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
654641 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
655642 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
656643
......@@ -696,7 +683,6 @@ test "@exp with vectors" {
696683 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
697684 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
698685 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
699 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
700686 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
701687 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
702688
......@@ -718,7 +704,6 @@ test "@exp2 f16" {
718704 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
719705 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
720706 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
721 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
722707 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
723708 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
724709
......@@ -730,7 +715,6 @@ test "@exp2 f32/f64" {
730715 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
731716 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
732717 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
733 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
734718 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
735719 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
736720
......@@ -771,7 +755,6 @@ test "@exp2 with @vectors" {
771755 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
772756 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
773757 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
774 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
775758 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
776759 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
777760
......@@ -793,7 +776,6 @@ test "@log f16" {
793776 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
794777 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
795778 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
796 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
797779 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
798780 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
799781
......@@ -805,7 +787,6 @@ test "@log f32/f64" {
805787 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
806788 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
807789 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
808 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
809790 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
810791 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
811792
......@@ -847,7 +828,6 @@ test "@log with @vectors" {
847828 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
848829 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
849830 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
850 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
851831 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
852832 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
853833
......@@ -866,7 +846,6 @@ test "@log2 f16" {
866846 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
867847 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
868848 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
869 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
870849 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
871850 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
872851
......@@ -878,7 +857,6 @@ test "@log2 f32/f64" {
878857 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
879858 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
880859 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
881 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
882860 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
883861 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
884862
......@@ -919,7 +897,6 @@ test "@log2 with vectors" {
919897 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
920898 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
921899 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
922 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
923900 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
924901 // https://github.com/ziglang/zig/issues/13681
925902 if (builtin.zig_backend == .stage2_llvm and
......@@ -945,7 +922,6 @@ test "@log10 f16" {
945922 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
946923 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
947924 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
948 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
949925 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
950926 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
951927
......@@ -957,7 +933,6 @@ test "@log10 f32/f64" {
957933 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
958934 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
959935 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
960 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
961936 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
962937 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
963938
......@@ -998,7 +973,6 @@ test "@log10 with vectors" {
998973 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
999974 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1000975 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1001 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1002976 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
1003977 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1004978
......@@ -1243,7 +1217,6 @@ test "@ceil f16" {
12431217 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
12441218 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
12451219 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1246 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
12471220 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
12481221
12491222 try testCeil(f16);
......@@ -1255,7 +1228,6 @@ test "@ceil f32/f64" {
12551228 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
12561229 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
12571230 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1258 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
12591231 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
12601232
12611233 try testCeil(f32);
......@@ -1320,7 +1292,6 @@ test "@ceil with vectors" {
13201292 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
13211293 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
13221294 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
1323 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
13241295 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
13251296 if (builtin.zig_backend == .stage2_x86_64 and
13261297 !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest;
......@@ -1344,7 +1315,6 @@ test "@trunc f16" {
13441315 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
13451316 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
13461317 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1347 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
13481318 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
13491319
13501320 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isMIPS()) {
......@@ -1361,7 +1331,6 @@ test "@trunc f32/f64" {
13611331 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
13621332 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
13631333 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1364 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
13651334 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
13661335
13671336 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isMIPS()) {
......@@ -1430,7 +1399,6 @@ fn testTrunc(comptime T: type) !void {
14301399test "@trunc with vectors" {
14311400 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
14321401 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1433 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
14341402 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
14351403 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
14361404 if (builtin.zig_backend == .stage2_x86_64 and
......@@ -1454,7 +1422,6 @@ test "neg f16" {
14541422 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
14551423 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
14561424 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1457 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
14581425 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
14591426 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
14601427 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
......@@ -1472,7 +1439,6 @@ test "neg f32/f64" {
14721439 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
14731440 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
14741441 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1475 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
14761442 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
14771443 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
14781444
test/behavior/hasdecl.zig+2
......@@ -13,6 +13,7 @@ const Bar = struct {
1313
1414test "@hasDecl" {
1515 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
16 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1617
1718 try expect(@hasDecl(Foo, "public_thing"));
1819 try expect(!@hasDecl(Foo, "private_thing"));
......@@ -25,6 +26,7 @@ test "@hasDecl" {
2526
2627test "@hasDecl using a sliced string literal" {
2728 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
29 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
2830
2931 try expect(@hasDecl(@This(), "std") == true);
3032 try expect(@hasDecl(@This(), "std"[0..0]) == false);
test/behavior/math.zig+54-4
......@@ -440,7 +440,6 @@ test "division" {
440440 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
441441 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
442442 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
443 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
444443 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
445444 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
446445 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
......@@ -530,7 +529,6 @@ test "division half-precision floats" {
530529 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
531530 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
532531 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
533 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
534532 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
535533 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
536534
......@@ -1030,6 +1028,60 @@ test "@mulWithOverflow bitsize > 32" {
10301028 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
10311029 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
10321030
1031 {
1032 var a: u40 = 3;
1033 var b: u40 = 0x55_5555_5555;
1034 var ov = @mulWithOverflow(a, b);
1035
1036 try expect(ov[0] == 0xff_ffff_ffff);
1037 try expect(ov[1] == 0);
1038
1039 // Check that overflow bits in the low-word of wide-multiplications are checked too.
1040 // Intermediate result is less than 2**64
1041 b = 0x55_5555_5556;
1042 ov = @mulWithOverflow(a, b);
1043 try expect(ov[0] == 2);
1044 try expect(ov[1] == 1);
1045
1046 // Check that overflow bits in the high-word of wide-multiplications are checked too.
1047 // Intermediate result is more than 2**64 and bits 40..64 are not set.
1048 a = 0x10_0000_0000;
1049 b = 0x10_0000_0000;
1050 ov = @mulWithOverflow(a, b);
1051 try expect(ov[0] == 0);
1052 try expect(ov[1] == 1);
1053 }
1054
1055 {
1056 var a: i40 = 3;
1057 var b: i40 = -0x2a_aaaa_aaaa;
1058 var ov = @mulWithOverflow(a, b);
1059
1060 try expect(ov[0] == -0x7f_ffff_fffe);
1061 try expect(ov[1] == 0);
1062
1063 // Check that the sign bit is properly checked
1064 b = -0x2a_aaaa_aaab;
1065 ov = @mulWithOverflow(a, b);
1066 try expect(ov[0] == 0x7f_ffff_ffff);
1067 try expect(ov[1] == 1);
1068
1069 // Check that the low-order bits above the sign are checked.
1070 a = 6;
1071 ov = @mulWithOverflow(a, b);
1072 try expect(ov[0] == -2);
1073 try expect(ov[1] == 1);
1074
1075 // Check that overflow bits in the high-word of wide-multiplications are checked too.
1076 // high parts and sign of low-order bits are all 1.
1077 a = 0x08_0000_0000;
1078 b = -0x08_0000_0001;
1079 ov = @mulWithOverflow(a, b);
1080
1081 try expect(ov[0] == -0x8_0000_0000);
1082 try expect(ov[1] == 1);
1083 }
1084
10331085 {
10341086 var a: u62 = 3;
10351087 _ = &a;
......@@ -1579,7 +1631,6 @@ test "@round f16" {
15791631 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
15801632 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
15811633 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1582 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
15831634 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
15841635 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
15851636
......@@ -1591,7 +1642,6 @@ test "@round f32/f64" {
15911642 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
15921643 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
15931644 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1594 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
15951645 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
15961646 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
15971647
test/behavior/optional.zig+2
......@@ -61,6 +61,7 @@ test "optional with zero-bit type" {
6161 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
6262 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
6363 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
64 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
6465
6566 const S = struct {
6667 fn doTheTest(comptime ZeroBit: type, comptime zero_bit: ZeroBit) !void {
......@@ -641,6 +642,7 @@ test "result location initialization of optional with OPV payload" {
641642 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
642643 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
643644 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
645 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
644646
645647 const S = struct {
646648 x: u0,
test/behavior/packed-struct.zig+2
......@@ -1306,6 +1306,8 @@ test "2-byte packed struct argument in C calling convention" {
13061306}
13071307
13081308test "packed struct contains optional pointer" {
1309 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1310
13091311 const foo: packed struct {
13101312 a: ?*@This() = null,
13111313 } = .{};
test/behavior/packed-union.zig+2
......@@ -177,6 +177,8 @@ test "assigning to non-active field at comptime" {
177177}
178178
179179test "comptime packed union of pointers" {
180 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
181
180182 const U = packed union {
181183 a: *const u32,
182184 b: *const [1]u32,
test/behavior/select.zig-2
......@@ -8,7 +8,6 @@ test "@select vectors" {
88 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
99 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1010 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
11 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1211 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1312
1413 try comptime selectVectors();
......@@ -39,7 +38,6 @@ test "@select arrays" {
3938 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
4039 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
4140 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
42 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
4341 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
4442 if (builtin.zig_backend == .stage2_x86_64 and
4543 !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .avx2)) return error.SkipZigTest;
test/behavior/shuffle.zig+83
......@@ -2,6 +2,7 @@ const std = @import("std");
22const builtin = @import("builtin");
33const mem = std.mem;
44const expect = std.testing.expect;
5const expectEqual = std.testing.expectEqual;
56
67test "@shuffle int" {
78 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
......@@ -49,6 +50,88 @@ test "@shuffle int" {
4950 try comptime S.doTheTest();
5051}
5152
53test "@shuffle int strange sizes" {
54 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
55 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
56 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
57 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
58 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
59 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
60
61 try comptime testShuffle(2, 2, 2);
62 try testShuffle(2, 2, 2);
63 try comptime testShuffle(4, 4, 4);
64 try testShuffle(4, 4, 4);
65 try comptime testShuffle(7, 4, 4);
66 try testShuffle(7, 4, 4);
67 try comptime testShuffle(8, 6, 4);
68 try testShuffle(8, 6, 4);
69 try comptime testShuffle(2, 7, 5);
70 try testShuffle(2, 7, 5);
71 try comptime testShuffle(13, 16, 12);
72 try testShuffle(13, 16, 12);
73 try comptime testShuffle(19, 3, 17);
74 try testShuffle(19, 3, 17);
75 try comptime testShuffle(1, 10, 1);
76 try testShuffle(1, 10, 1);
77}
78
79fn testShuffle(
80 comptime x_len: comptime_int,
81 comptime a_len: comptime_int,
82 comptime b_len: comptime_int,
83) !void {
84 const T = i32;
85 const XT = @Vector(x_len, T);
86 const AT = @Vector(a_len, T);
87 const BT = @Vector(b_len, T);
88
89 const a_elems = comptime blk: {
90 var elems: [a_len]T = undefined;
91 for (&elems, 0..) |*elem, i| elem.* = @intCast(100 + i);
92 break :blk elems;
93 };
94 var a: AT = a_elems;
95 _ = &a;
96
97 const b_elems = comptime blk: {
98 var elems: [b_len]T = undefined;
99 for (&elems, 0..) |*elem, i| elem.* = @intCast(1000 + i);
100 break :blk elems;
101 };
102 var b: BT = b_elems;
103 _ = &b;
104
105 const mask_seed: []const i32 = &.{ -14, -31, 23, 1, 21, 13, 17, -21, -10, -27, -16, -5, 15, 14, -2, 26, 2, -31, -24, -16 };
106
107 const mask = comptime blk: {
108 var elems: [x_len]i32 = undefined;
109 for (&elems, 0..) |*elem, i| {
110 const mask_val = mask_seed[i];
111 if (mask_val >= 0) {
112 elem.* = @mod(mask_val, a_len);
113 } else {
114 elem.* = @mod(mask_val, -b_len);
115 }
116 }
117
118 break :blk elems;
119 };
120
121 const x: XT = @shuffle(T, a, b, mask);
122
123 const x_elems: [x_len]T = x;
124 for (mask, x_elems) |m, x_elem| {
125 if (m >= 0) {
126 // Element from A
127 try expectEqual(x_elem, a_elems[@intCast(m)]);
128 } else {
129 // Element from B
130 try expectEqual(x_elem, b_elems[@intCast(~m)]);
131 }
132 }
133}
134
52135test "@shuffle bool 1" {
53136 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
54137 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
test/behavior/slice.zig+3
......@@ -408,6 +408,7 @@ test "slice syntax resulting in pointer-to-array" {
408408 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
409409 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
410410 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
411 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
411412
412413 const S = struct {
413414 fn doTheTest() !void {
......@@ -863,6 +864,7 @@ test "global slice field access" {
863864
864865test "slice of void" {
865866 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
867 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
866868
867869 var n: usize = 10;
868870 _ = &n;
......@@ -988,6 +990,7 @@ test "get address of element of zero-sized slice" {
988990 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
989991 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
990992 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
993 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
991994
992995 const S = struct {
993996 fn destroy(_: *void) void {}
test/behavior/string_literals.zig+2
......@@ -35,6 +35,7 @@ test "@typeName() returns a string literal" {
3535 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
3636 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
3737 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
38 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
3839
3940 try std.testing.expect(*const [type_name.len:0]u8 == @TypeOf(type_name));
4041 try std.testing.expect(std.mem.eql(u8, "behavior.string_literals.TestType", type_name));
......@@ -49,6 +50,7 @@ test "@embedFile() returns a string literal" {
4950 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
5051 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
5152 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
53 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
5254
5355 try std.testing.expect(*const [expected_contents.len:0]u8 == @TypeOf(actual_contents));
5456 try std.testing.expect(std.mem.eql(u8, expected_contents, actual_contents));
test/behavior/typename.zig+5
......@@ -43,6 +43,7 @@ test "anon field init" {
4343 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
4444 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
4545 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
46 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
4647
4748 const Foo = .{
4849 .T1 = struct {},
......@@ -91,6 +92,7 @@ test "top level decl" {
9192 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
9293 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
9394 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
95 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
9496
9597 try expectEqualStrings(
9698 "behavior.typename.A_Struct",
......@@ -141,6 +143,7 @@ test "fn param" {
141143 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
142144 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
143145 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
146 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
144147
145148 // https://github.com/ziglang/zig/issues/675
146149 try expectEqualStrings(
......@@ -221,6 +224,7 @@ test "local variable" {
221224 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
222225 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
223226 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
227 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
224228
225229 const Foo = struct { a: u32 };
226230 const Bar = union { a: u32 };
......@@ -250,6 +254,7 @@ test "anon name strategy used in sub expression" {
250254 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
251255 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
252256 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
257 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
253258
254259 const S = struct {
255260 fn getTheName() []const u8 {
test/behavior/union.zig+1
......@@ -920,6 +920,7 @@ test "union no tag with struct member" {
920920 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
921921 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
922922 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
923 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
923924
924925 const Struct = struct {};
925926 const Union = union {
test/behavior/vector.zig+1-1
......@@ -268,6 +268,7 @@ test "tuple to vector" {
268268 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
269269 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
270270 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
271 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
271272
272273 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) {
273274 // Regressed with LLVM 14:
......@@ -547,7 +548,6 @@ test "vector division operators" {
547548 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
548549 if (builtin.zig_backend == .stage2_llvm and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest;
549550 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
550 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
551551 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
552552
553553 const S = struct {