authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-06-03 00:44:08+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-06-10 20:32:49+02:00
loga3b1ba82f57d5d8981a471850cbbb0db29c3a479
tree848b20d5c8929a1198f331725865f1a08c07288d
parent4e7159ae1d08ce74548e0adc3b3936aacc23a06e
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: new vectorization helper

The old vectorization helper (WipElementWise) was clunky and a bit annoying to use, and it wasn't really flexible enough. This introduces a new vectorization helper, which uses Temporary and Operation types to deduce a Vectorization to perform the operation in a reasonably efficient manner. It removes the outer loop required by WipElementWise so that implementations of AIR instructions are cleaner. This helps with sanity when we start to introduce support for composite integers. airShift, convertToDirect, convertToIndirect, and normalize are initially implemented using this new method.

7 files changed, 1647 insertions(+), 1067 deletions(-)

src/codegen/spirv.zig+1578-1017
......@@ -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;
......@@ -423,6 +424,17 @@ const DeclGen = struct {
423424 return self.fail("TODO (SPIR-V): " ++ format, args);
424425 }
425426
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
426438 /// Fetch the result-id for a previously generated instruction or constant.
427439 fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef {
428440 const mod = self.module;
......@@ -631,6 +643,19 @@ const DeclGen = struct {
631643 const mod = self.module;
632644 const target = self.getTarget();
633645 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
634659 const elem_ty = ty.childType(mod);
635660
636661 const len = ty.vectorLen(mod);
......@@ -723,9 +748,13 @@ const DeclGen = struct {
723748 // Use backing bits so that negatives are sign extended
724749 const backing_bits = self.backingIntBits(int_info.bits).?; // Assertion failure means big int
725750
726 const bits: u64 = switch (int_info.signedness) {
727 // Intcast needed to silence compile errors for when the wrong path is compiled.
728 // 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) {
729758 .signed => @bitCast(@as(i64, @intCast(value))),
730759 .unsigned => @as(u64, @intCast(value)),
731760 };
......@@ -1392,6 +1421,19 @@ const DeclGen = struct {
13921421 return ty_id;
13931422 }
13941423
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
13951437 /// Generate a union type. Union types are always generated with the
13961438 /// most aligned field active. If the tag alignment is greater
13971439 /// than that of the payload, a regular union (non-packed, with both tag and
......@@ -1928,77 +1970,897 @@ const DeclGen = struct {
19281970 return union_layout;
19291971 }
19301972
1931 /// This structure is used as helper for element-wise operations. It is intended
1932 /// to be used with vectors, fake vectors (arrays) and single elements.
1933 const WipElementWise = struct {
1934 dg: *DeclGen,
1935 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.
19361983 ty: Type,
1937 /// Always in direct representation.
1938 ty_id: IdRef,
1939 /// True if the input is an array type.
1940 is_array: bool,
1941 /// The element-wise operation should fill these results before calling finalize().
1942 /// These should all be in **direct** representation! `finalize()` will convert
1943 /// them to indirect if required.
1944 results: []IdRef,
1945
1946 fn deinit(wip: *WipElementWise) void {
1947 wip.dg.gpa.free(wip.results);
1948 }
1949
1950 /// Utility function to extract the element at a particular index in an
1951 /// input array. This type is expected to be a fake vector (array) if `wip.is_array`, and
1952 /// a vector or scalar otherwise.
1953 fn elementAt(wip: WipElementWise, ty: Type, value: IdRef, index: usize) !IdRef {
1954 const mod = wip.dg.module;
1955 if (wip.is_array) {
1956 assert(ty.isVector(mod));
1957 return try wip.dg.extractVectorComponent(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) };
19582094 } else {
1959 assert(index == 0);
1960 return value;
2095 return .{ .unrolled = ty.vectorLen(mod) };
19612096 }
19622097 }
19632098
1964 /// Turns the results of this WipElementWise into a result. This can be
1965 /// vectors, fake vectors (arrays) and single elements, depending on `result_ty`.
1966 /// After calling this function, this WIP is no longer usable.
1967 /// Results is in `direct` representation.
1968 fn finalize(wip: *WipElementWise) !IdRef {
1969 if (wip.is_array) {
1970 return try wip.dg.constructVector(wip.result_ty, wip.results);
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;
2120 }
19712121 } else {
1972 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 }
19732129 }
19742130 }
19752131
1976 /// Allocate a result id at a particular index, and return it.
1977 fn allocId(wip: *WipElementWise, index: usize) IdRef {
1978 assert(wip.is_array or index == 0);
1979 wip.results[index] = wip.dg.spv.allocId();
1980 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 };
2139 }
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 };
19812252 }
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 };
19822312 };
19832313
1984 /// Create a new element-wise operation.
1985 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 {
19862341 const mod = self.module;
1987 const is_array = result_ty.isVector(mod) and (!self.isSpvVector(result_ty) or force_element_wise);
1988 const num_results = if (is_array) result_ty.vectorLen(mod) else 1;
1989 const results = try self.gpa.alloc(IdRef, num_results);
1990 @memset(results, undefined);
19912342
1992 const ty = if (is_array) result_ty.scalarType(mod) else result_ty;
1993 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 }
19942445
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();
2670
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);
19952861 return .{
1996 .dg = self,
1997 .result_ty = result_ty,
1998 .ty = ty,
1999 .ty_id = ty_id,
2000 .is_array = is_array,
2001 .results = results,
2862 v.finalize(result_ty, value_results),
2863 v.finalize(result_ty, overflow_results),
20022864 };
20032865 }
20042866
......@@ -2237,59 +3099,42 @@ const DeclGen = struct {
22373099 }
22383100 }
22393101
2240 fn intFromBool(self: *DeclGen, ty: Type, condition_id: IdRef) !IdRef {
2241 const zero_id = try self.constInt(ty, 0, .direct);
2242 const one_id = try self.constInt(ty, 1, .direct);
2243 const result_id = self.spv.allocId();
2244 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2245 .id_result_type = try self.resolveType(ty, .direct),
2246 .id_result = result_id,
2247 .condition = condition_id,
2248 .object_1 = one_id,
2249 .object_2 = zero_id,
2250 });
2251 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 );
22523115 }
22533116
22543117 /// Convert representation from indirect (in memory) to direct (in 'register')
22553118 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).
22563119 fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
22573120 const mod = self.module;
2258 const scalar_ty = ty.scalarType(mod);
2259 const is_spv_vector = self.isSpvVector(ty);
2260 switch (scalar_ty.zigTypeTag(mod)) {
3121 switch (ty.scalarType(mod).zigTypeTag(mod)) {
22613122 .Bool => {
2262 // TODO: We may want to use something like elementWise in this function.
2263 // First we need to audit whether this would recursively call into itself.
2264 if (!ty.isVector(mod) or is_spv_vector) {
2265 const result_id = self.spv.allocId();
2266 const scalar_false_id = try self.constBool(false, .indirect);
2267 const false_id = if (is_spv_vector) blk: {
2268 const index = try mod.intern_pool.get(mod.gpa, .{
2269 .vector_type = .{
2270 .len = ty.vectorLen(mod),
2271 .child = Type.u1.toIntern(),
2272 },
2273 });
2274 const vec_ty = Type.fromInterned(index);
2275 break :blk try self.constructVectorSplat(vec_ty, scalar_false_id);
2276 } else scalar_false_id;
2277
2278 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
2279 .id_result_type = try self.resolveType(ty, .direct),
2280 .id_result = result_id,
2281 .operand_1 = operand_id,
2282 .operand_2 = false_id,
2283 });
2284 return result_id;
2285 }
2286
2287 const constituents = try self.gpa.alloc(IdRef, ty.vectorLen(mod));
2288 for (constituents, 0..) |*id, i| {
2289 const element = try self.extractVectorComponent(scalar_ty, operand_id, @intCast(i));
2290 id.* = try self.convertToDirect(scalar_ty, element);
2291 }
2292 return try self.constructVector(ty, constituents);
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);
22933138 },
22943139 else => return operand_id,
22953140 }
......@@ -2299,55 +3144,10 @@ const DeclGen = struct {
22993144 /// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect).
23003145 fn convertToIndirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
23013146 const mod = self.module;
2302 const scalar_ty = ty.scalarType(mod);
2303 const is_spv_vector = self.isSpvVector(ty);
2304 switch (scalar_ty.zigTypeTag(mod)) {
3147 switch (ty.scalarType(mod).zigTypeTag(mod)) {
23053148 .Bool => {
2306 const result_ty = if (is_spv_vector) blk: {
2307 const index = try mod.intern_pool.get(mod.gpa, .{
2308 .vector_type = .{
2309 .len = ty.vectorLen(mod),
2310 .child = Type.u1.toIntern(),
2311 },
2312 });
2313 break :blk Type.fromInterned(index);
2314 } else Type.u1;
2315
2316 if (!ty.isVector(mod) or is_spv_vector) {
2317 // TODO: We may want to use something like elementWise in this function.
2318 // First we need to audit whether this would recursively call into itself.
2319 // Also unify it with intFromBool
2320
2321 const scalar_zero_id = try self.constInt(Type.u1, 0, .direct);
2322 const scalar_one_id = try self.constInt(Type.u1, 1, .direct);
2323
2324 const zero_id = if (is_spv_vector)
2325 try self.constructVectorSplat(result_ty, scalar_zero_id)
2326 else
2327 scalar_zero_id;
2328
2329 const one_id = if (is_spv_vector)
2330 try self.constructVectorSplat(result_ty, scalar_one_id)
2331 else
2332 scalar_one_id;
2333
2334 const result_id = self.spv.allocId();
2335 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2336 .id_result_type = try self.resolveType(result_ty, .direct),
2337 .id_result = result_id,
2338 .condition = operand_id,
2339 .object_1 = one_id,
2340 .object_2 = zero_id,
2341 });
2342 return result_id;
2343 }
2344
2345 const constituents = try self.gpa.alloc(IdRef, ty.vectorLen(mod));
2346 for (constituents, 0..) |*id, i| {
2347 const element = try self.extractVectorComponent(scalar_ty, operand_id, @intCast(i));
2348 id.* = try self.convertToIndirect(scalar_ty, element);
2349 }
2350 return try self.constructVector(result_ty, constituents);
3149 const result = try self.intFromBool(Temporary.init(ty, operand_id));
3150 return try result.materialize(self);
23513151 },
23523152 else => return operand_id,
23533153 }
......@@ -2428,26 +3228,35 @@ const DeclGen = struct {
24283228 const air_tags = self.air.instructions.items(.tag);
24293229 const maybe_result_id: ?IdRef = switch (air_tags[@intFromEnum(inst)]) {
24303230 // zig fmt: off
2431 .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),
2432 .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
2433 .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
2434
2435
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),
24363244 .abs => try self.airAbs(inst),
2437 .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),
24383250
2439 .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),
24403254
2441 .div_float,
2442 .div_float_optimized,
2443 .div_trunc,
2444 .div_trunc_optimized => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv),
2445 .rem, .rem_optimized => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem),
2446 .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),
24473257
2448
2449 .add_with_overflow => try self.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan),
2450 .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),
24513260 .mul_with_overflow => try self.airMulOverflow(inst),
24523261 .shl_with_overflow => try self.airShlOverflow(inst),
24533262
......@@ -2456,6 +3265,8 @@ const DeclGen = struct {
24563265 .ctz => try self.airClzCtz(inst, .ctz),
24573266 .clz => try self.airClzCtz(inst, .clz),
24583267
3268 .select => try self.airSelect(inst),
3269
24593270 .splat => try self.airSplat(inst),
24603271 .reduce, .reduce_optimized => try self.airReduce(inst),
24613272 .shuffle => try self.airShuffle(inst),
......@@ -2463,17 +3274,17 @@ const DeclGen = struct {
24633274 .ptr_add => try self.airPtrAdd(inst),
24643275 .ptr_sub => try self.airPtrSub(inst),
24653276
2466 .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),
2467 .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),
2468 .xor => try self.airBinOpSimple(inst, .OpBitwiseXor),
2469 .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd),
2470 .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),
24713282
2472 .shl, .shl_exact => try self.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical),
2473 .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),
24743285
2475 .min => try self.airMinMax(inst, .lt),
2476 .max => try self.airMinMax(inst, .gt),
3286 .min => try self.airMinMax(inst, .min),
3287 .max => try self.airMinMax(inst, .max),
24773288
24783289 .bitcast => try self.airBitCast(inst),
24793290 .intcast, .trunc => try self.airIntCast(inst),
......@@ -2574,39 +3385,23 @@ const DeclGen = struct {
25743385 try self.inst_results.putNoClobber(self.gpa, inst, result_id);
25753386 }
25763387
2577 fn binOpSimple(self: *DeclGen, ty: Type, lhs_id: IdRef, rhs_id: IdRef, comptime opcode: Opcode) !IdRef {
2578 var wip = try self.elementWise(ty, false);
2579 defer wip.deinit();
2580 for (0..wip.results.len) |i| {
2581 try self.func.body.emit(self.spv.gpa, opcode, .{
2582 .id_result_type = wip.ty_id,
2583 .id_result = wip.allocId(i),
2584 .operand_1 = try wip.elementAt(ty, lhs_id, i),
2585 .operand_2 = try wip.elementAt(ty, rhs_id, i),
2586 });
2587 }
2588 return try wip.finalize();
2589 }
2590
2591 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef {
3388 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, op: BinaryOp) !?IdRef {
25923389 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2593 const lhs_id = try self.resolve(bin_op.lhs);
2594 const rhs_id = try self.resolve(bin_op.rhs);
2595 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);
25963392
2597 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);
25983395 }
25993396
2600 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 {
26013398 const mod = self.module;
26023399 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2603 const lhs_id = try self.resolve(bin_op.lhs);
2604 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);
26053403
26063404 const result_ty = self.typeOfIndex(inst);
2607 const shift_ty = self.typeOf(bin_op.rhs);
2608 const scalar_result_ty_id = try self.resolveType(result_ty.scalarType(mod), .direct);
2609 const scalar_shift_ty_id = try self.resolveType(shift_ty.scalarType(mod), .direct);
26103405
26113406 const info = self.arithmeticTypeInfo(result_ty);
26123407 switch (info.class) {
......@@ -2615,121 +3410,58 @@ const DeclGen = struct {
26153410 .float, .bool => unreachable,
26163411 }
26173412
2618 var wip = try self.elementWise(result_ty, false);
2619 defer wip.deinit();
2620 for (wip.results, 0..) |*result_id, i| {
2621 const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i);
2622 const rhs_elem_id = try wip.elementAt(shift_ty, rhs_id, i);
2623
2624 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
2625 // so just manually upcast it if required.
2626 const shift_id = if (scalar_shift_ty_id != scalar_result_ty_id) blk: {
2627 const shift_id = self.spv.allocId();
2628 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
2629 .id_result_type = wip.ty_id,
2630 .id_result = shift_id,
2631 .unsigned_value = rhs_elem_id,
2632 });
2633 break :blk shift_id;
2634 } else rhs_elem_id;
2635
2636 const value_id = self.spv.allocId();
2637 const args = .{
2638 .id_result_type = wip.ty_id,
2639 .id_result = value_id,
2640 .base = lhs_elem_id,
2641 .shift = shift_id,
2642 };
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.
26433415
2644 if (result_ty.isSignedInt(mod)) {
2645 try self.func.body.emit(self.spv.gpa, signed, args);
2646 } else {
2647 try self.func.body.emit(self.spv.gpa, unsigned, args);
2648 }
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);
26493420
2650 result_id.* = try self.normalize(wip.ty, value_id, info);
2651 }
2652 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);
26533428 }
26543429
2655 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 {
26563433 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2657 const lhs_id = try self.resolve(bin_op.lhs);
2658 const rhs_id = try self.resolve(bin_op.rhs);
2659 const result_ty = self.typeOfIndex(inst);
26603434
2661 return try self.minMax(result_ty, op, lhs_id, rhs_id);
2662 }
3435 const lhs = try self.temporary(bin_op.lhs);
3436 const rhs = try self.temporary(bin_op.rhs);
26633437
2664 fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef {
2665 const info = self.arithmeticTypeInfo(result_ty);
2666 const target = self.getTarget();
3438 const result = try self.minMax(lhs, rhs, op);
3439 return try result.materialize(self);
3440 }
26673441
2668 const use_backup_codegen = target.os.tag == .opencl and info.class != .float;
2669 var wip = try self.elementWise(result_ty, use_backup_codegen);
2670 defer wip.deinit();
3442 fn minMax(self: *DeclGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary {
3443 const info = self.arithmeticTypeInfo(lhs.ty);
26713444
2672 for (wip.results, 0..) |*result_id, i| {
2673 const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i);
2674 const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i);
2675
2676 if (use_backup_codegen) {
2677 const cmp_id = try self.cmp(op, Type.bool, wip.ty, lhs_elem_id, rhs_elem_id);
2678 result_id.* = self.spv.allocId();
2679 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2680 .id_result_type = wip.ty_id,
2681 .id_result = result_id.*,
2682 .condition = cmp_id,
2683 .object_1 = lhs_elem_id,
2684 .object_2 = rhs_elem_id,
2685 });
2686 } else {
2687 const ext_inst: Word = switch (target.os.tag) {
2688 .opencl => switch (op) {
2689 .lt => 28, // fmin
2690 .gt => 27, // fmax
2691 else => unreachable,
2692 },
2693 .vulkan => switch (info.class) {
2694 .float => switch (op) {
2695 .lt => 37, // FMin
2696 .gt => 40, // FMax
2697 else => unreachable,
2698 },
2699 .integer, .strange_integer => switch (info.signedness) {
2700 .signed => switch (op) {
2701 .lt => 39, // SMin
2702 .gt => 42, // SMax
2703 else => unreachable,
2704 },
2705 .unsigned => switch (op) {
2706 .lt => 38, // UMin
2707 .gt => 41, // UMax
2708 else => unreachable,
2709 },
2710 },
2711 .composite_integer => unreachable, // TODO
2712 .bool => unreachable,
2713 },
2714 else => unreachable,
2715 };
2716 const set_id = switch (target.os.tag) {
2717 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
2718 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),
2719 else => unreachable,
2720 };
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 };
27213463
2722 result_id.* = self.spv.allocId();
2723 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2724 .id_result_type = wip.ty_id,
2725 .id_result = result_id.*,
2726 .set = set_id,
2727 .instruction = .{ .inst = ext_inst },
2728 .id_ref_4 = &.{ lhs_elem_id, rhs_elem_id },
2729 });
2730 }
2731 }
2732 return wip.finalize();
3464 return try self.buildBinary(binop, lhs, rhs);
27333465 }
27343466
27353467 /// This function normalizes values to a canonical representation
......@@ -2740,41 +3472,24 @@ const DeclGen = struct {
27403472 /// - Signed integers are also sign extended if they are negative.
27413473 /// All other values are returned unmodified (this makes strange integer
27423474 /// wrapping easier to use in generic operations).
2743 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;
27443478 switch (info.class) {
2745 .integer, .bool, .float => return value_id,
3479 .integer, .bool, .float => return value,
27463480 .composite_integer => unreachable, // TODO
27473481 .strange_integer => switch (info.signedness) {
27483482 .unsigned => {
27493483 const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1;
2750 const result_id = self.spv.allocId();
2751 const mask_id = try self.constInt(ty, mask_value, .direct);
2752 try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{
2753 .id_result_type = try self.resolveType(ty, .direct),
2754 .id_result = result_id,
2755 .operand_1 = value_id,
2756 .operand_2 = mask_id,
2757 });
2758 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));
27593486 },
27603487 .signed => {
27613488 // Shift left and right so that we can copy the sight bit that way.
2762 const shift_amt_id = try self.constInt(ty, info.backing_bits - info.bits, .direct);
2763 const left_id = self.spv.allocId();
2764 try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{
2765 .id_result_type = try self.resolveType(ty, .direct),
2766 .id_result = left_id,
2767 .base = value_id,
2768 .shift = shift_amt_id,
2769 });
2770 const right_id = self.spv.allocId();
2771 try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{
2772 .id_result_type = try self.resolveType(ty, .direct),
2773 .id_result = right_id,
2774 .base = left_id,
2775 .shift = shift_amt_id,
2776 });
2777 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);
27783493 },
27793494 },
27803495 }
......@@ -2782,491 +3497,438 @@ const DeclGen = struct {
27823497
27833498 fn airDivFloor(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
27843499 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2785 const lhs_id = try self.resolve(bin_op.lhs);
2786 const rhs_id = try self.resolve(bin_op.rhs);
2787 const ty = self.typeOfIndex(inst);
2788 const ty_id = try self.resolveType(ty, .direct);
2789 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);
27903505 switch (info.class) {
27913506 .composite_integer => unreachable, // TODO
27923507 .integer, .strange_integer => {
2793 const zero_id = try self.constInt(ty, 0, .direct);
2794 const one_id = try self.constInt(ty, 1, .direct);
2795
2796 // (a ^ b) > 0
2797 const bin_bitwise_id = try self.binOpSimple(ty, lhs_id, rhs_id, .OpBitwiseXor);
2798 const is_positive_id = try self.cmp(.gt, Type.bool, ty, bin_bitwise_id, zero_id);
2799
2800 // a / b
2801 const positive_div_id = try self.arithOp(ty, lhs_id, rhs_id, .OpFDiv, .OpSDiv, .OpUDiv);
2802
2803 // - (abs(a) + abs(b) - 1) / abs(b)
2804 const lhs_abs = try self.abs(ty, ty, lhs_id);
2805 const rhs_abs = try self.abs(ty, ty, rhs_id);
2806 const negative_div_lhs = try self.arithOp(
2807 ty,
2808 try self.arithOp(ty, lhs_abs, rhs_abs, .OpFAdd, .OpIAdd, .OpIAdd),
2809 one_id,
2810 .OpFSub,
2811 .OpISub,
2812 .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,
28133536 );
2814 const negative_div_id = try self.arithOp(ty, negative_div_lhs, rhs_abs, .OpFDiv, .OpSDiv, .OpUDiv);
2815 const negated_negative_div_id = self.spv.allocId();
2816 try self.func.body.emit(self.spv.gpa, .OpSNegate, .{
2817 .id_result_type = ty_id,
2818 .id_result = negated_negative_div_id,
2819 .operand = negative_div_id,
2820 });
28213537
2822 const result_id = self.spv.allocId();
2823 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
2824 .id_result_type = ty_id,
2825 .id_result = result_id,
2826 .condition = is_positive_id,
2827 .object_1 = positive_div_id,
2828 .object_2 = negated_negative_div_id,
2829 });
2830 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);
28313545 },
28323546 .float => {
2833 const div_id = try self.arithOp(ty, lhs_id, rhs_id, .OpFDiv, .OpSDiv, .OpUDiv);
2834 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);
28353550 },
28363551 .bool => unreachable,
28373552 }
28383553 }
28393554
2840 fn airFloor(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2841 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2842 const operand_id = try self.resolve(un_op);
2843 const result_ty = self.typeOfIndex(inst);
2844 return try self.floor(result_ty, operand_id);
2845 }
3555 fn airDivTrunc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3556 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
28463557
2847 fn floor(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
2848 const target = self.getTarget();
2849 const ty_id = try self.resolveType(ty, .direct);
2850 const ext_inst: Word = switch (target.os.tag) {
2851 .opencl => 25,
2852 .vulkan => 8,
2853 else => unreachable,
2854 };
2855 const set_id = switch (target.os.tag) {
2856 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
2857 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),
2858 else => unreachable,
2859 };
3558 const lhs = try self.temporary(bin_op.lhs);
3559 const rhs = try self.temporary(bin_op.rhs);
28603560
2861 const result_id = self.spv.allocId();
2862 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2863 .id_result_type = ty_id,
2864 .id_result = result_id,
2865 .set = set_id,
2866 .instruction = .{ .inst = ext_inst },
2867 .id_ref_4 = &.{operand_id},
2868 });
2869 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);
28703588 }
28713589
28723590 fn airArithOp(
28733591 self: *DeclGen,
28743592 inst: Air.Inst.Index,
2875 comptime fop: Opcode,
2876 comptime sop: Opcode,
2877 comptime uop: Opcode,
3593 comptime fop: BinaryOp,
3594 comptime sop: BinaryOp,
3595 comptime uop: BinaryOp,
28783596 ) !?IdRef {
2879 // LHS and RHS are guaranteed to have the same type, and AIR guarantees
2880 // the result to be the same as the LHS and RHS, which matches SPIR-V.
2881 const ty = self.typeOfIndex(inst);
28823597 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2883 const lhs_id = try self.resolve(bin_op.lhs);
2884 const rhs_id = try self.resolve(bin_op.rhs);
2885
2886 assert(self.typeOf(bin_op.lhs).eql(ty, self.module));
2887 assert(self.typeOf(bin_op.rhs).eql(ty, self.module));
28883598
2889 return try self.arithOp(ty, lhs_id, rhs_id, fop, sop, uop);
2890 }
3599 const lhs = try self.temporary(bin_op.lhs);
3600 const rhs = try self.temporary(bin_op.rhs);
28913601
2892 fn arithOp(
2893 self: *DeclGen,
2894 ty: Type,
2895 lhs_id: IdRef,
2896 rhs_id: IdRef,
2897 comptime fop: Opcode,
2898 comptime sop: Opcode,
2899 comptime uop: Opcode,
2900 ) !IdRef {
2901 // Binary operations are generally applicable to both scalar and vector operations
2902 // in SPIR-V, but int and float versions of operations require different opcodes.
2903 const info = self.arithmeticTypeInfo(ty);
3602 const info = self.arithmeticTypeInfo(lhs.ty);
29043603
2905 const opcode_index: usize = switch (info.class) {
2906 .composite_integer => {
2907 return self.todo("binary operations for composite integers", .{});
2908 },
3604 const result = switch (info.class) {
3605 .composite_integer => unreachable, // TODO
29093606 .integer, .strange_integer => switch (info.signedness) {
2910 .signed => 1,
2911 .unsigned => 2,
3607 .signed => try self.buildBinary(sop, lhs, rhs),
3608 .unsigned => try self.buildBinary(uop, lhs, rhs),
29123609 },
2913 .float => 0,
3610 .float => try self.buildBinary(fop, lhs, rhs),
29143611 .bool => unreachable,
29153612 };
29163613
2917 var wip = try self.elementWise(ty, false);
2918 defer wip.deinit();
2919 for (wip.results, 0..) |*result_id, i| {
2920 const lhs_elem_id = try wip.elementAt(ty, lhs_id, i);
2921 const rhs_elem_id = try wip.elementAt(ty, rhs_id, i);
2922
2923 const value_id = self.spv.allocId();
2924 const operands = .{
2925 .id_result_type = wip.ty_id,
2926 .id_result = value_id,
2927 .operand_1 = lhs_elem_id,
2928 .operand_2 = rhs_elem_id,
2929 };
2930
2931 switch (opcode_index) {
2932 0 => try self.func.body.emit(self.spv.gpa, fop, operands),
2933 1 => try self.func.body.emit(self.spv.gpa, sop, operands),
2934 2 => try self.func.body.emit(self.spv.gpa, uop, operands),
2935 else => unreachable,
2936 }
2937
2938 // TODO: Trap on overflow? Probably going to be annoying.
2939 // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
2940 result_id.* = try self.normalize(wip.ty, value_id, info);
2941 }
2942
2943 return try wip.finalize();
3614 return try result.materialize(self);
29443615 }
29453616
29463617 fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
29473618 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2948 const operand_id = try self.resolve(ty_op.operand);
3619 const operand = try self.temporary(ty_op.operand);
29493620 // Note: operand_ty may be signed, while ty is always unsigned!
2950 const operand_ty = self.typeOf(ty_op.operand);
29513621 const result_ty = self.typeOfIndex(inst);
2952 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);
29533624 }
29543625
2955 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 {
29563627 const target = self.getTarget();
2957 const operand_info = self.arithmeticTypeInfo(operand_ty);
3628 const operand_info = self.arithmeticTypeInfo(value.ty);
29583629
2959 var wip = try self.elementWise(result_ty, false);
2960 defer wip.deinit();
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);
29613634
2962 for (wip.results, 0..) |*result_id, i| {
2963 const elem_id = try wip.elementAt(operand_ty, operand_id, i);
2964
2965 const ext_inst: Word = switch (target.os.tag) {
2966 .opencl => switch (operand_info.class) {
2967 .float => 23, // fabs
2968 .integer, .strange_integer => switch (operand_info.signedness) {
2969 .signed => 141, // s_abs
2970 .unsigned => 201, // u_abs
2971 },
2972 .composite_integer => unreachable, // TODO
2973 .bool => unreachable,
2974 },
2975 .vulkan => switch (operand_info.class) {
2976 .float => 4, // FAbs
2977 .integer, .strange_integer => 5, // SAbs
2978 .composite_integer => unreachable, // TODO
2979 .bool => unreachable,
2980 },
2981 else => unreachable,
2982 };
2983 const set_id = switch (target.os.tag) {
2984 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
2985 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),
2986 else => unreachable,
2987 };
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
29883640
2989 result_id.* = self.spv.allocId();
2990 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
2991 .id_result_type = wip.ty_id,
2992 .id_result = result_id.*,
2993 .set = set_id,
2994 .instruction = .{ .inst = ext_inst },
2995 .id_ref_4 = &.{elem_id},
2996 });
3641 return try self.normalize(abs_value, self.arithmeticTypeInfo(result_ty));
3642 },
3643 .composite_integer => unreachable, // TODO
3644 .bool => unreachable,
29973645 }
2998 return try wip.finalize();
29993646 }
30003647
30013648 fn airAddSubOverflow(
30023649 self: *DeclGen,
30033650 inst: Air.Inst.Index,
3004 comptime add: Opcode,
3005 comptime ucmp: Opcode,
3006 comptime scmp: Opcode,
3651 comptime add: BinaryOp,
3652 comptime ucmp: CmpPredicate,
3653 comptime scmp: CmpPredicate,
30073654 ) !?IdRef {
3008 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
30093661 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
30103662 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3011 const lhs = try self.resolve(extra.lhs);
3012 const rhs = try self.resolve(extra.rhs);
30133663
3014 const result_ty = self.typeOfIndex(inst);
3015 const operand_ty = self.typeOf(extra.lhs);
3016 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);
30173666
3018 const bool_ty_id = try self.resolveType(Type.bool, .direct);
3019 const cmp_ty_id = if (self.isSpvVector(operand_ty))
3020 // TODO: Resolving a vector type with .direct should return a SPIR-V vector
3021 try self.spv.vectorType(operand_ty.vectorLen(mod), try self.resolveType(Type.bool, .direct))
3022 else
3023 bool_ty_id;
3667 const result_ty = self.typeOfIndex(inst);
30243668
3025 const info = self.arithmeticTypeInfo(operand_ty);
3669 const info = self.arithmeticTypeInfo(lhs.ty);
30263670 switch (info.class) {
3027 .composite_integer => return self.todo("overflow ops for composite integers", .{}),
3671 .composite_integer => unreachable, // TODO
30283672 .strange_integer, .integer => {},
30293673 .float, .bool => unreachable,
30303674 }
30313675
3032 var wip_result = try self.elementWise(operand_ty, false);
3033 defer wip_result.deinit();
3034 var wip_ov = try self.elementWise(ov_ty, false);
3035 defer wip_ov.deinit();
3036 for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| {
3037 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);
3038 const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i);
3039
3040 // Normalize both so that we can properly check for overflow
3041 const value_id = self.spv.allocId();
3042
3043 try self.func.body.emit(self.spv.gpa, add, .{
3044 .id_result_type = wip_result.ty_id,
3045 .id_result = value_id,
3046 .operand_1 = lhs_elem_id,
3047 .operand_2 = rhs_elem_id,
3048 });
3049
3050 // Normalize the result so that the comparisons go well
3051 result_id.* = try self.normalize(wip_result.ty, value_id, info);
3052
3053 const overflowed_id = switch (info.signedness) {
3054 .unsigned => blk: {
3055 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
3056 // For subtraction the conditions need to be swapped.
3057 const overflowed_id = self.spv.allocId();
3058 try self.func.body.emit(self.spv.gpa, ucmp, .{
3059 .id_result_type = cmp_ty_id,
3060 .id_result = overflowed_id,
3061 .operand_1 = result_id.*,
3062 .operand_2 = lhs_elem_id,
3063 });
3064 break :blk overflowed_id;
3065 },
3066 .signed => blk: {
3067 // lhs - rhs
3068 // For addition, overflow happened if:
3069 // - rhs is negative and value > lhs
3070 // - rhs is positive and value < lhs
3071 // This can be shortened to:
3072 // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs)
3073 // = (rhs < 0) == (value > lhs)
3074 // = (rhs < 0) == (lhs < value)
3075 // Note that signed overflow is also wrapping in spir-v.
3076 // For subtraction, overflow happened if:
3077 // - rhs is negative and value < lhs
3078 // - rhs is positive and value > lhs
3079 // This can be shortened to:
3080 // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs)
3081 // = (rhs < 0) == (value < lhs)
3082 // = (rhs < 0) == (lhs > value)
3083
3084 const rhs_lt_zero_id = self.spv.allocId();
3085 const zero_id = try self.constInt(wip_result.ty, 0, .direct);
3086 try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{
3087 .id_result_type = cmp_ty_id,
3088 .id_result = rhs_lt_zero_id,
3089 .operand_1 = rhs_elem_id,
3090 .operand_2 = zero_id,
3091 });
3092
3093 const value_gt_lhs_id = self.spv.allocId();
3094 try self.func.body.emit(self.spv.gpa, scmp, .{
3095 .id_result_type = cmp_ty_id,
3096 .id_result = value_gt_lhs_id,
3097 .operand_1 = lhs_elem_id,
3098 .operand_2 = result_id.*,
3099 });
3100
3101 const overflowed_id = self.spv.allocId();
3102 try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{
3103 .id_result_type = cmp_ty_id,
3104 .id_result = overflowed_id,
3105 .operand_1 = rhs_lt_zero_id,
3106 .operand_2 = value_gt_lhs_id,
3107 });
3108 break :blk overflowed_id;
3109 },
3110 };
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 };
31113705
3112 ov_id.* = try self.intFromBool(wip_ov.ty, overflowed_id);
3113 }
3706 const ov = try self.intFromBool(overflowed);
31143707
31153708 return try self.constructStruct(
31163709 result_ty,
3117 &.{ operand_ty, ov_ty },
3118 &.{ try wip_result.finalize(), try wip_ov.finalize() },
3710 &.{ result.ty, ov.ty },
3711 &.{ try result.materialize(self), try ov.materialize(self) },
31193712 );
31203713 }
31213714
31223715 fn airMulOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3716 const target = self.getTarget();
3717 const mod = self.module;
3718
31233719 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
31243720 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3125 const lhs = try self.resolve(extra.lhs);
3126 const rhs = try self.resolve(extra.rhs);
3721
3722 const lhs = try self.temporary(extra.lhs);
3723 const rhs = try self.temporary(extra.rhs);
31273724
31283725 const result_ty = self.typeOfIndex(inst);
3129 const operand_ty = self.typeOf(extra.lhs);
3130 const ov_ty = result_ty.structFieldType(1, self.module);
31313726
3132 const info = self.arithmeticTypeInfo(operand_ty);
3727 const info = self.arithmeticTypeInfo(lhs.ty);
31333728 switch (info.class) {
3134 .composite_integer => return self.todo("overflow ops for composite integers", .{}),
3729 .composite_integer => unreachable, // TODO
31353730 .strange_integer, .integer => {},
31363731 .float, .bool => unreachable,
31373732 }
31383733
3139 var wip_result = try self.elementWise(operand_ty, true);
3140 defer wip_result.deinit();
3141 var wip_ov = try self.elementWise(ov_ty, true);
3142 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 };
31433750
3144 const zero_id = try self.constInt(wip_result.ty, 0, .direct);
3145 const zero_ov_id = try self.constInt(wip_ov.ty, 0, .direct);
3146 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);
31473757
3148 for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| {
3149 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);
3150 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);
31513759
3152 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);
31533762
3154 // (a != 0) and (x / a != b)
3155 const not_zero_id = try self.cmp(.neq, Type.bool, wip_result.ty, lhs_elem_id, zero_id);
3156 const res_rhs_id = try self.arithOp(wip_result.ty, result_id.*, lhs_elem_id, .OpFDiv, .OpSDiv, .OpUDiv);
3157 const res_rhs_not_rhs_id = try self.cmp(.neq, Type.bool, wip_result.ty, res_rhs_id, rhs_elem_id);
3158 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);
31593766
3160 ov_id.* = self.spv.allocId();
3161 try self.func.body.emit(self.spv.gpa, .OpSelect, .{
3162 .id_result_type = wip_ov.ty_id,
3163 .id_result = ov_id.*,
3164 .condition = cond_id,
3165 .object_1 = one_ov_id,
3166 .object_2 = zero_ov_id,
3167 });
3168 }
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);
31693887
31703888 return try self.constructStruct(
31713889 result_ty,
3172 &.{ operand_ty, ov_ty },
3173 &.{ try wip_result.finalize(), try wip_ov.finalize() },
3890 &.{ result.ty, ov.ty },
3891 &.{ try result.materialize(self), try ov.materialize(self) },
31743892 );
31753893 }
31763894
31773895 fn airShlOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
31783896 const mod = self.module;
3897
31793898 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
31803899 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3181 const lhs = try self.resolve(extra.lhs);
3182 const rhs = try self.resolve(extra.rhs);
3183
3184 const result_ty = self.typeOfIndex(inst);
3185 const operand_ty = self.typeOf(extra.lhs);
3186 const shift_ty = self.typeOf(extra.rhs);
3187 const scalar_shift_ty_id = try self.resolveType(shift_ty.scalarType(mod), .direct);
3188 const scalar_operand_ty_id = try self.resolveType(operand_ty.scalarType(mod), .direct);
31893900
3190 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);
31913903
3192 const bool_ty_id = try self.resolveType(Type.bool, .direct);
3193 const cmp_ty_id = if (self.isSpvVector(operand_ty))
3194 // TODO: Resolving a vector type with .direct should return a SPIR-V vector
3195 try self.spv.vectorType(operand_ty.vectorLen(mod), try self.resolveType(Type.bool, .direct))
3196 else
3197 bool_ty_id;
3904 const result_ty = self.typeOfIndex(inst);
31983905
3199 const info = self.arithmeticTypeInfo(operand_ty);
3906 const info = self.arithmeticTypeInfo(base.ty);
32003907 switch (info.class) {
3201 .composite_integer => return self.todo("overflow shift for composite integers", .{}),
3908 .composite_integer => unreachable, // TODO
32023909 .integer, .strange_integer => {},
32033910 .float, .bool => unreachable,
32043911 }
32053912
3206 var wip_result = try self.elementWise(operand_ty, false);
3207 defer wip_result.deinit();
3208 var wip_ov = try self.elementWise(ov_ty, false);
3209 defer wip_ov.deinit();
3210 for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| {
3211 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);
3212 const rhs_elem_id = try wip_result.elementAt(shift_ty, rhs, i);
3213
3214 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3215 // so just manually upcast it if required.
3216 const shift_id = if (scalar_shift_ty_id != scalar_operand_ty_id) blk: {
3217 const shift_id = self.spv.allocId();
3218 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
3219 .id_result_type = wip_result.ty_id,
3220 .id_result = shift_id,
3221 .unsigned_value = rhs_elem_id,
3222 });
3223 break :blk shift_id;
3224 } else rhs_elem_id;
3225
3226 const value_id = self.spv.allocId();
3227 try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{
3228 .id_result_type = wip_result.ty_id,
3229 .id_result = value_id,
3230 .base = lhs_elem_id,
3231 .shift = shift_id,
3232 });
3233 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);
32343916
3235 const right_shift_id = self.spv.allocId();
3236 switch (info.signedness) {
3237 .signed => {
3238 try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{
3239 .id_result_type = wip_result.ty_id,
3240 .id_result = right_shift_id,
3241 .base = result_id.*,
3242 .shift = shift_id,
3243 });
3244 },
3245 .unsigned => {
3246 try self.func.body.emit(self.spv.gpa, .OpShiftRightLogical, .{
3247 .id_result_type = wip_result.ty_id,
3248 .id_result = right_shift_id,
3249 .base = result_id.*,
3250 .shift = shift_id,
3251 });
3252 },
3253 }
3917 const left = try self.buildBinary(.sll, base, casted_shift);
3918 const result = try self.normalize(left, info);
32543919
3255 const overflowed_id = self.spv.allocId();
3256 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{
3257 .id_result_type = cmp_ty_id,
3258 .id_result = overflowed_id,
3259 .operand_1 = lhs_elem_id,
3260 .operand_2 = right_shift_id,
3261 });
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 };
32623924
3263 ov_id.* = try self.intFromBool(wip_ov.ty, overflowed_id);
3264 }
3925 const overflowed = try self.buildCmp(.i_ne, base, right);
3926 const ov = try self.intFromBool(overflowed);
32653927
32663928 return try self.constructStruct(
32673929 result_ty,
3268 &.{ operand_ty, ov_ty },
3269 &.{ try wip_result.finalize(), try wip_ov.finalize() },
3930 &.{ result.ty, ov.ty },
3931 &.{ try result.materialize(self), try ov.materialize(self) },
32703932 );
32713933 }
32723934
......@@ -3274,122 +3936,67 @@ const DeclGen = struct {
32743936 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
32753937 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
32763938
3277 const mulend1 = try self.resolve(extra.lhs);
3278 const mulend2 = try self.resolve(extra.rhs);
3279 const addend = try self.resolve(pl_op.operand);
3280
3281 const ty = self.typeOfIndex(inst);
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);
32823942
3283 const info = self.arithmeticTypeInfo(ty);
3943 const result_ty = self.typeOfIndex(inst);
3944 const info = self.arithmeticTypeInfo(result_ty);
32843945 assert(info.class == .float); // .mul_add is only emitted for floats
32853946
3286 var wip = try self.elementWise(ty, false);
3287 defer wip.deinit();
3288 for (0..wip.results.len) |i| {
3289 const mul_result = self.spv.allocId();
3290 try self.func.body.emit(self.spv.gpa, .OpFMul, .{
3291 .id_result_type = wip.ty_id,
3292 .id_result = mul_result,
3293 .operand_1 = try wip.elementAt(ty, mulend1, i),
3294 .operand_2 = try wip.elementAt(ty, mulend2, i),
3295 });
3296
3297 try self.func.body.emit(self.spv.gpa, .OpFAdd, .{
3298 .id_result_type = wip.ty_id,
3299 .id_result = wip.allocId(i),
3300 .operand_1 = mul_result,
3301 .operand_2 = try wip.elementAt(ty, addend, i),
3302 });
3303 }
3304 return try wip.finalize();
3947 const result = try self.buildFma(a, b, c);
3948 return try result.materialize(self);
33053949 }
33063950
3307 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 {
33083952 if (self.liveness.isUnused(inst)) return null;
33093953
33103954 const mod = self.module;
33113955 const target = self.getTarget();
33123956 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3313 const result_ty = self.typeOfIndex(inst);
3314 const operand_ty = self.typeOf(ty_op.operand);
3315 const operand = try self.resolve(ty_op.operand);
3957 const operand = try self.temporary(ty_op.operand);
33163958
3317 const info = self.arithmeticTypeInfo(operand_ty);
3959 const scalar_result_ty = self.typeOfIndex(inst).scalarType(mod);
3960
3961 const info = self.arithmeticTypeInfo(operand.ty);
33183962 switch (info.class) {
33193963 .composite_integer => unreachable, // TODO
33203964 .integer, .strange_integer => {},
33213965 .float, .bool => unreachable,
33223966 }
33233967
3324 var wip = try self.elementWise(result_ty, false);
3325 defer wip.deinit();
3326
3327 const elem_ty = if (wip.is_array) operand_ty.scalarType(mod) else operand_ty;
3328 const elem_ty_id = try self.resolveType(elem_ty, .direct);
3329
3330 for (wip.results, 0..) |*result_id, i| {
3331 const elem = try wip.elementAt(operand_ty, operand, i);
3332
3333 switch (target.os.tag) {
3334 .opencl => {
3335 const set = try self.spv.importInstructionSet(.@"OpenCL.std");
3336 const ext_inst: u32 = switch (op) {
3337 .clz => 151, // clz
3338 .ctz => 152, // ctz
3339 };
3968 switch (target.os.tag) {
3969 .vulkan => unreachable, // TODO
3970 else => {},
3971 }
33403972
3341 // Note: result of OpenCL ctz/clz returns operand_ty, and we want result_ty.
3342 // result_ty is always large enough to hold the result, so we might have to down
3343 // cast it.
3344 const tmp = self.spv.allocId();
3345 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
3346 .id_result_type = elem_ty_id,
3347 .id_result = tmp,
3348 .set = set,
3349 .instruction = .{ .inst = ext_inst },
3350 .id_ref_4 = &.{elem},
3351 });
3973 const count = try self.buildUnary(op, operand);
33523974
3353 // TODO: Comparison should be removed..
3354 // Its valid because SpvModule caches numeric types
3355 if (wip.ty_id == elem_ty_id) {
3356 result_id.* = tmp;
3357 continue;
3358 }
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 }
33593981
3360 result_id.* = self.spv.allocId();
3361 if (result_ty.scalarType(mod).isSignedInt(mod)) {
3362 assert(elem_ty.scalarType(mod).isSignedInt(mod));
3363 try self.func.body.emit(self.spv.gpa, .OpSConvert, .{
3364 .id_result_type = wip.ty_id,
3365 .id_result = result_id.*,
3366 .signed_value = tmp,
3367 });
3368 } else {
3369 assert(elem_ty.scalarType(mod).isUnsignedInt(mod));
3370 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
3371 .id_result_type = wip.ty_id,
3372 .id_result = result_id.*,
3373 .unsigned_value = tmp,
3374 });
3375 }
3376 },
3377 .vulkan => unreachable, // TODO
3378 else => unreachable,
3379 }
3380 }
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);
33813988
3382 return try wip.finalize();
3989 const result = try self.buildSelect(pred, a, b);
3990 return try result.materialize(self);
33833991 }
33843992
33853993 fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
33863994 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3995
33873996 const operand_id = try self.resolve(ty_op.operand);
33883997 const result_ty = self.typeOfIndex(inst);
3389 var wip = try self.elementWise(result_ty, true);
3390 defer wip.deinit();
3391 @memset(wip.results, operand_id);
3392 return try wip.finalize();
3998
3999 return try self.constructVectorSplat(result_ty, operand_id);
33934000 }
33944001
33954002 fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -3402,23 +4009,33 @@ const DeclGen = struct {
34024009
34034010 const info = self.arithmeticTypeInfo(operand_ty);
34044011
3405 var result_id = try self.extractVectorComponent(scalar_ty, operand, 0);
34064012 const len = operand_ty.vectorLen(mod);
34074013
4014 const first = try self.extractVectorComponent(scalar_ty, operand, 0);
4015
34084016 switch (reduce.operation) {
34094017 .Min, .Max => |op| {
3410 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 };
34114024 for (1..len) |i| {
3412 const lhs = result_id;
3413 const rhs = try self.extractVectorComponent(scalar_ty, operand, @intCast(i));
3414 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);
34154030 }
34164031
3417 return result_id;
4032 return try result.materialize(self);
34184033 },
34194034 else => {},
34204035 }
34214036
4037 var result_id = first;
4038
34224039 const opcode: Opcode = switch (info.class) {
34234040 .bool => switch (reduce.operation) {
34244041 .And => .OpLogicalAnd,
......@@ -3602,50 +4219,66 @@ const DeclGen = struct {
36024219 fn cmp(
36034220 self: *DeclGen,
36044221 op: std.math.CompareOperator,
3605 result_ty: Type,
3606 ty: Type,
3607 lhs_id: IdRef,
3608 rhs_id: IdRef,
3609 ) !IdRef {
4222 lhs: Temporary,
4223 rhs: Temporary,
4224 ) !Temporary {
36104225 const mod = self.module;
3611 var cmp_lhs_id = lhs_id;
3612 var cmp_rhs_id = rhs_id;
3613 const bool_ty_id = try self.resolveType(Type.bool, .direct);
3614 const op_ty = switch (ty.zigTypeTag(mod)) {
3615 .Int, .Bool, .Float => ty,
3616 .Enum => ty.intTagType(mod),
3617 .ErrorSet => Type.u16,
3618 .Pointer => blk: {
4226 const scalar_ty = lhs.ty.scalarType(mod);
4227 const is_vector = lhs.ty.isVector(mod);
4228
4229 switch (scalar_ty.zigTypeTag(mod)) {
4230 .Int, .Bool, .Float => {},
4231 .Enum => {
4232 assert(!is_vector);
4233 const ty = lhs.ty.intTagType(mod);
4234 return try self.cmp(op, lhs.pun(ty), rhs.pun(ty));
4235 },
4236 .ErrorSet => {
4237 assert(!is_vector);
4238 return try self.cmp(op, lhs.pun(Type.u16), rhs.pun(Type.u16));
4239 },
4240 .Pointer => {
4241 assert(!is_vector);
36194242 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are
36204243 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using
36214244 // OpConvertPtrToU...
3622 cmp_lhs_id = self.spv.allocId();
3623 cmp_rhs_id = self.spv.allocId();
36244245
36254246 const usize_ty_id = try self.resolveType(Type.usize, .direct);
36264247
4248 const lhs_int_id = self.spv.allocId();
36274249 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
36284250 .id_result_type = usize_ty_id,
3629 .id_result = cmp_lhs_id,
3630 .pointer = lhs_id,
4251 .id_result = lhs_int_id,
4252 .pointer = try lhs.materialize(self),
36314253 });
36324254
4255 const rhs_int_id = self.spv.allocId();
36334256 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
36344257 .id_result_type = usize_ty_id,
3635 .id_result = cmp_rhs_id,
3636 .pointer = rhs_id,
4258 .id_result = rhs_int_id,
4259 .pointer = try rhs.materialize(self),
36374260 });
36384261
3639 break :blk Type.usize;
4262 const lhs_int = Temporary.init(Type.usize, lhs_int_id);
4263 const rhs_int = Temporary.init(Type.usize, rhs_int_id);
4264 return try self.cmp(op, lhs_int, rhs_int);
36404265 },
36414266 .Optional => {
4267 assert(!is_vector);
4268
4269 const ty = lhs.ty;
4270
36424271 const payload_ty = ty.optionalChild(mod);
36434272 if (ty.optionalReprIsPayload(mod)) {
36444273 assert(payload_ty.hasRuntimeBitsIgnoreComptime(mod));
36454274 assert(!payload_ty.isSlice(mod));
3646 return self.cmp(op, Type.bool, payload_ty, lhs_id, rhs_id);
4275
4276 return try self.cmp(op, lhs.pun(payload_ty), rhs.pun(payload_ty));
36474277 }
36484278
4279 const lhs_id = try lhs.materialize(self);
4280 const rhs_id = try rhs.materialize(self);
4281
36494282 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod))
36504283 try self.extractField(Type.bool, lhs_id, 1)
36514284 else
......@@ -3656,8 +4289,11 @@ const DeclGen = struct {
36564289 else
36574290 try self.convertToDirect(Type.bool, rhs_id);
36584291
4292 const lhs_valid = Temporary.init(Type.bool, lhs_valid_id);
4293 const rhs_valid = Temporary.init(Type.bool, rhs_valid_id);
4294
36594295 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
3660 return try self.cmp(op, Type.bool, Type.bool, lhs_valid_id, rhs_valid_id);
4296 return try self.cmp(op, lhs_valid, rhs_valid);
36614297 }
36624298
36634299 // a = lhs_valid
......@@ -3678,118 +4314,71 @@ const DeclGen = struct {
36784314 const lhs_pl_id = try self.extractField(payload_ty, lhs_id, 0);
36794315 const rhs_pl_id = try self.extractField(payload_ty, rhs_id, 0);
36804316
3681 switch (op) {
3682 .eq => {
3683 const valid_eq_id = try self.cmp(.eq, Type.bool, Type.bool, lhs_valid_id, rhs_valid_id);
3684 const pl_eq_id = try self.cmp(op, Type.bool, payload_ty, lhs_pl_id, rhs_pl_id);
3685 const lhs_not_valid_id = self.spv.allocId();
3686 try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{
3687 .id_result_type = bool_ty_id,
3688 .id_result = lhs_not_valid_id,
3689 .operand = lhs_valid_id,
3690 });
3691 const impl_id = self.spv.allocId();
3692 try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{
3693 .id_result_type = bool_ty_id,
3694 .id_result = impl_id,
3695 .operand_1 = lhs_not_valid_id,
3696 .operand_2 = pl_eq_id,
3697 });
3698 const result_id = self.spv.allocId();
3699 try self.func.body.emit(self.spv.gpa, .OpLogicalAnd, .{
3700 .id_result_type = bool_ty_id,
3701 .id_result = result_id,
3702 .operand_1 = valid_eq_id,
3703 .operand_2 = impl_id,
3704 });
3705 return result_id;
3706 },
3707 .neq => {
3708 const valid_neq_id = try self.cmp(.neq, Type.bool, Type.bool, lhs_valid_id, rhs_valid_id);
3709 const pl_neq_id = try self.cmp(op, Type.bool, payload_ty, lhs_pl_id, rhs_pl_id);
3710
3711 const impl_id = self.spv.allocId();
3712 try self.func.body.emit(self.spv.gpa, .OpLogicalAnd, .{
3713 .id_result_type = bool_ty_id,
3714 .id_result = impl_id,
3715 .operand_1 = lhs_valid_id,
3716 .operand_2 = pl_neq_id,
3717 });
3718 const result_id = self.spv.allocId();
3719 try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{
3720 .id_result_type = bool_ty_id,
3721 .id_result = result_id,
3722 .operand_1 = valid_neq_id,
3723 .operand_2 = impl_id,
3724 });
3725 return result_id;
3726 },
4317 const lhs_pl = Temporary.init(payload_ty, lhs_pl_id);
4318 const rhs_pl = Temporary.init(payload_ty, rhs_pl_id);
4319
4320 return switch (op) {
4321 .eq => try self.buildBinary(
4322 .l_and,
4323 try self.cmp(.eq, lhs_valid, rhs_valid),
4324 try self.buildBinary(
4325 .l_or,
4326 try self.buildUnary(.l_not, lhs_valid),
4327 try self.cmp(.eq, lhs_pl, rhs_pl),
4328 ),
4329 ),
4330 .neq => try self.buildBinary(
4331 .l_or,
4332 try self.cmp(.neq, lhs_valid, rhs_valid),
4333 try self.buildBinary(
4334 .l_and,
4335 lhs_valid,
4336 try self.cmp(.neq, lhs_pl, rhs_pl),
4337 ),
4338 ),
37274339 else => unreachable,
3728 }
3729 },
3730 .Vector => {
3731 var wip = try self.elementWise(result_ty, true);
3732 defer wip.deinit();
3733 const scalar_ty = ty.scalarType(mod);
3734 for (wip.results, 0..) |*result_id, i| {
3735 const lhs_elem_id = try wip.elementAt(ty, lhs_id, i);
3736 const rhs_elem_id = try wip.elementAt(ty, rhs_id, i);
3737 result_id.* = try self.cmp(op, Type.bool, scalar_ty, lhs_elem_id, rhs_elem_id);
3738 }
3739 return wip.finalize();
4340 };
37404341 },
37414342 else => unreachable,
3742 };
4343 }
37434344
3744 const opcode: Opcode = opcode: {
3745 const info = self.arithmeticTypeInfo(op_ty);
3746 const signedness = switch (info.class) {
3747 .composite_integer => {
3748 return self.todo("binary operations for composite integers", .{});
3749 },
3750 .float => break :opcode switch (op) {
3751 .eq => .OpFOrdEqual,
3752 .neq => .OpFUnordNotEqual,
3753 .lt => .OpFOrdLessThan,
3754 .lte => .OpFOrdLessThanEqual,
3755 .gt => .OpFOrdGreaterThan,
3756 .gte => .OpFOrdGreaterThanEqual,
3757 },
3758 .bool => break :opcode switch (op) {
3759 .eq => .OpLogicalEqual,
3760 .neq => .OpLogicalNotEqual,
3761 else => unreachable,
4345 const info = self.arithmeticTypeInfo(scalar_ty);
4346 const pred: CmpPredicate = switch (info.class) {
4347 .composite_integer => unreachable, // TODO
4348 .float => switch (op) {
4349 .eq => .f_oeq,
4350 .neq => .f_une,
4351 .lt => .f_olt,
4352 .lte => .f_ole,
4353 .gt => .f_ogt,
4354 .gte => .f_oge,
4355 },
4356 .bool => switch (op) {
4357 .eq => .l_eq,
4358 .neq => .l_ne,
4359 else => unreachable,
4360 },
4361 .integer, .strange_integer => switch (info.signedness) {
4362 .signed => switch (op) {
4363 .eq => .i_eq,
4364 .neq => .i_ne,
4365 .lt => .s_lt,
4366 .lte => .s_le,
4367 .gt => .s_gt,
4368 .gte => .s_ge,
37624369 },
3763 .integer, .strange_integer => info.signedness,
3764 };
3765
3766 break :opcode switch (signedness) {
37674370 .unsigned => switch (op) {
3768 .eq => .OpIEqual,
3769 .neq => .OpINotEqual,
3770 .lt => .OpULessThan,
3771 .lte => .OpULessThanEqual,
3772 .gt => .OpUGreaterThan,
3773 .gte => .OpUGreaterThanEqual,
3774 },
3775 .signed => switch (op) {
3776 .eq => .OpIEqual,
3777 .neq => .OpINotEqual,
3778 .lt => .OpSLessThan,
3779 .lte => .OpSLessThanEqual,
3780 .gt => .OpSGreaterThan,
3781 .gte => .OpSGreaterThanEqual,
4371 .eq => .i_eq,
4372 .neq => .i_ne,
4373 .lt => .u_lt,
4374 .lte => .u_le,
4375 .gt => .u_gt,
4376 .gte => .u_ge,
37824377 },
3783 };
4378 },
37844379 };
37854380
3786 const result_id = self.spv.allocId();
3787 try self.func.body.emitRaw(self.spv.gpa, opcode, 4);
3788 self.func.body.writeOperand(spec.IdResultType, bool_ty_id);
3789 self.func.body.writeOperand(spec.IdResult, result_id);
3790 self.func.body.writeOperand(spec.IdResultType, cmp_lhs_id);
3791 self.func.body.writeOperand(spec.IdResultType, cmp_rhs_id);
3792 return result_id;
4381 return try self.buildCmp(pred, lhs, rhs);
37934382 }
37944383
37954384 fn airCmp(
......@@ -3798,24 +4387,22 @@ const DeclGen = struct {
37984387 comptime op: std.math.CompareOperator,
37994388 ) !?IdRef {
38004389 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3801 const lhs_id = try self.resolve(bin_op.lhs);
3802 const rhs_id = try self.resolve(bin_op.rhs);
3803 const ty = self.typeOf(bin_op.lhs);
3804 const result_ty = self.typeOfIndex(inst);
4390 const lhs = try self.temporary(bin_op.lhs);
4391 const rhs = try self.temporary(bin_op.rhs);
38054392
3806 return try self.cmp(op, result_ty, ty, lhs_id, rhs_id);
4393 const result = try self.cmp(op, lhs, rhs);
4394 return try result.materialize(self);
38074395 }
38084396
38094397 fn airVectorCmp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
38104398 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
38114399 const vec_cmp = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;
3812 const lhs_id = try self.resolve(vec_cmp.lhs);
3813 const rhs_id = try self.resolve(vec_cmp.rhs);
4400 const lhs = try self.temporary(vec_cmp.lhs);
4401 const rhs = try self.temporary(vec_cmp.rhs);
38144402 const op = vec_cmp.compareOperator();
3815 const ty = self.typeOf(vec_cmp.lhs);
3816 const result_ty = self.typeOfIndex(inst);
38174403
3818 return try self.cmp(op, result_ty, ty, lhs_id, rhs_id);
4404 const result = try self.cmp(op, lhs, rhs);
4405 return try result.materialize(self);
38194406 }
38204407
38214408 /// Bitcast one type to another. Note: both types, input, output are expected in **direct** representation.
......@@ -3881,7 +4468,8 @@ const DeclGen = struct {
38814468 // should we change the representation of strange integers?
38824469 if (dst_ty.zigTypeTag(mod) == .Int) {
38834470 const info = self.arithmeticTypeInfo(dst_ty);
3884 return try self.normalize(dst_ty, result_id, info);
4471 const result = try self.normalize(Temporary.init(dst_ty, result_id), info);
4472 return try result.materialize(self);
38854473 }
38864474
38874475 return result_id;
......@@ -3897,46 +4485,28 @@ const DeclGen = struct {
38974485
38984486 fn airIntCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
38994487 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3900 const operand_id = try self.resolve(ty_op.operand);
3901 const src_ty = self.typeOf(ty_op.operand);
4488 const src = try self.temporary(ty_op.operand);
39024489 const dst_ty = self.typeOfIndex(inst);
39034490
3904 const src_info = self.arithmeticTypeInfo(src_ty);
4491 const src_info = self.arithmeticTypeInfo(src.ty);
39054492 const dst_info = self.arithmeticTypeInfo(dst_ty);
39064493
39074494 if (src_info.backing_bits == dst_info.backing_bits) {
3908 return operand_id;
4495 return try src.materialize(self);
39094496 }
39104497
3911 var wip = try self.elementWise(dst_ty, false);
3912 defer wip.deinit();
3913 for (wip.results, 0..) |*result_id, i| {
3914 const elem_id = try wip.elementAt(src_ty, operand_id, i);
3915 const value_id = self.spv.allocId();
3916 switch (dst_info.signedness) {
3917 .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{
3918 .id_result_type = wip.ty_id,
3919 .id_result = value_id,
3920 .signed_value = elem_id,
3921 }),
3922 .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
3923 .id_result_type = wip.ty_id,
3924 .id_result = value_id,
3925 .unsigned_value = elem_id,
3926 }),
3927 }
4498 const converted = try self.buildIntConvert(dst_ty, src);
39284499
3929 // Make sure to normalize the result if shrinking.
3930 // Because strange ints are sign extended in their backing
3931 // type, we don't need to normalize when growing the type. The
3932 // representation is already the same.
3933 if (dst_info.bits < src_info.bits) {
3934 result_id.* = try self.normalize(wip.ty, value_id, dst_info);
3935 } else {
3936 result_id.* = value_id;
3937 }
3938 }
3939 return try wip.finalize();
4500 // Make sure to normalize the result if shrinking.
4501 // Because strange ints are sign extended in their backing
4502 // type, we don't need to normalize when growing the type. The
4503 // representation is already the same.
4504 const result = if (dst_info.bits < src_info.bits)
4505 try self.normalize(converted, dst_info)
4506 else
4507 converted;
4508
4509 return try result.materialize(self);
39404510 }
39414511
39424512 fn intFromPtr(self: *DeclGen, operand_id: IdRef) !IdRef {
......@@ -4011,16 +4581,9 @@ const DeclGen = struct {
40114581
40124582 fn airIntFromBool(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
40134583 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4014 const operand_id = try self.resolve(un_op);
4015 const result_ty = self.typeOfIndex(inst);
4016
4017 var wip = try self.elementWise(result_ty, false);
4018 defer wip.deinit();
4019 for (wip.results, 0..) |*result_id, i| {
4020 const elem_id = try wip.elementAt(Type.bool, operand_id, i);
4021 result_id.* = try self.intFromBool(wip.ty, elem_id);
4022 }
4023 return try wip.finalize();
4584 const operand = try self.temporary(un_op);
4585 const result = try self.intFromBool(operand);
4586 return try result.materialize(self);
40244587 }
40254588
40264589 fn airFloatCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -4040,33 +4603,21 @@ const DeclGen = struct {
40404603
40414604 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
40424605 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4043 const operand_id = try self.resolve(ty_op.operand);
4606 const operand = try self.temporary(ty_op.operand);
40444607 const result_ty = self.typeOfIndex(inst);
40454608 const info = self.arithmeticTypeInfo(result_ty);
40464609
4047 var wip = try self.elementWise(result_ty, false);
4048 defer wip.deinit();
4049
4050 for (0..wip.results.len) |i| {
4051 const args = .{
4052 .id_result_type = wip.ty_id,
4053 .id_result = wip.allocId(i),
4054 .operand = try wip.elementAt(result_ty, operand_id, i),
4055 };
4056 switch (info.class) {
4057 .bool => {
4058 try self.func.body.emit(self.spv.gpa, .OpLogicalNot, args);
4059 },
4060 .float => unreachable,
4061 .composite_integer => unreachable, // TODO
4062 .strange_integer, .integer => {
4063 // Note: strange integer bits will be masked before operations that do not hold under modulo.
4064 try self.func.body.emit(self.spv.gpa, .OpNot, args);
4065 },
4066 }
4067 }
4610 const result = switch (info.class) {
4611 .bool => try self.buildUnary(.l_not, operand),
4612 .float => unreachable,
4613 .composite_integer => unreachable, // TODO
4614 .strange_integer, .integer => blk: {
4615 const complement = try self.buildUnary(.bit_not, operand);
4616 break :blk try self.normalize(complement, info);
4617 },
4618 };
40684619
4069 return try wip.finalize();
4620 return try result.materialize(self);
40704621 }
40714622
40724623 fn airArrayToSlice(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
......@@ -4338,8 +4889,11 @@ const DeclGen = struct {
43384889 // For now, just generate a temporary and use that.
43394890 // TODO: This backend probably also should use isByRef from llvm...
43404891
4892 const is_vector = array_ty.isVector(mod);
4893
4894 const elem_repr: Repr = if (is_vector) .direct else .indirect;
43414895 const ptr_array_ty_id = try self.ptrType2(array_ty, .Function, .direct);
4342 const ptr_elem_ty_id = try self.ptrType2(elem_ty, .Function, .direct);
4896 const ptr_elem_ty_id = try self.ptrType2(elem_ty, .Function, elem_repr);
43434897
43444898 const tmp_id = self.spv.allocId();
43454899 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
......@@ -4357,12 +4911,12 @@ const DeclGen = struct {
43574911
43584912 const result_id = self.spv.allocId();
43594913 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
4360 .id_result_type = try self.resolveType(elem_ty, .direct),
4914 .id_result_type = try self.resolveType(elem_ty, elem_repr),
43614915 .id_result = result_id,
43624916 .pointer = elem_ptr_id,
43634917 });
43644918
4365 if (array_ty.isVector(mod)) {
4919 if (is_vector) {
43664920 // Result is already in direct representation
43674921 return result_id;
43684922 }
......@@ -4585,7 +5139,10 @@ const DeclGen = struct {
45855139 if (field_offset == 0) break :base_ptr_int field_ptr_int;
45865140
45875141 const field_offset_id = try self.constInt(Type.usize, field_offset, .direct);
4588 break :base_ptr_int try self.binOpSimple(Type.usize, field_ptr_int, field_offset_id, .OpISub);
5142 const field_ptr_tmp = Temporary.init(Type.usize, field_ptr_int);
5143 const field_offset_tmp = Temporary.init(Type.usize, field_offset_id);
5144 const result = try self.buildBinary(.i_sub, field_ptr_tmp, field_offset_tmp);
5145 break :base_ptr_int try result.materialize(self);
45895146 };
45905147
45915148 const base_ptr = self.spv.allocId();
......@@ -5400,13 +5957,17 @@ const DeclGen = struct {
54005957 else
54015958 loaded_id;
54025959
5403 const payload_ty_id = try self.resolveType(ptr_ty, .direct);
5404 const null_id = try self.spv.constNull(payload_ty_id);
5960 const ptr_ty_id = try self.resolveType(ptr_ty, .direct);
5961 const null_id = try self.spv.constNull(ptr_ty_id);
5962 const null_tmp = Temporary.init(ptr_ty, null_id);
5963 const ptr = Temporary.init(ptr_ty, ptr_id);
5964
54055965 const op: std.math.CompareOperator = switch (pred) {
54065966 .is_null => .eq,
54075967 .is_non_null => .neq,
54085968 };
5409 return try self.cmp(op, Type.bool, ptr_ty, ptr_id, null_id);
5969 const result = try self.cmp(op, ptr, null_tmp);
5970 return try result.materialize(self);
54105971 }
54115972
54125973 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 {
test/behavior/abs.zig-1
......@@ -152,7 +152,6 @@ test "@abs int vectors" {
152152 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
153153 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
154154 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
155 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
156155
157156 try comptime testAbsIntVectors(1);
158157 try testAbsIntVectors(1);
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/math.zig+54-5
......@@ -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
......@@ -622,7 +620,6 @@ test "negation wrapping" {
622620 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
623621 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
624622 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
625 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
626623
627624 try expectEqual(@as(u1, 1), negateWrap(u1, 1));
628625}
......@@ -1031,6 +1028,60 @@ test "@mulWithOverflow bitsize > 32" {
10311028 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
10321029 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
10331030
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
10341085 {
10351086 var a: u62 = 3;
10361087 _ = &a;
......@@ -1580,7 +1631,6 @@ test "@round f16" {
15801631 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
15811632 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
15821633 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1583 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
15841634 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
15851635 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
15861636
......@@ -1592,7 +1642,6 @@ test "@round f32/f64" {
15921642 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
15931643 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
15941644 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1595 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
15961645 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
15971646 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
15981647
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/vector.zig-1
......@@ -548,7 +548,6 @@ test "vector division operators" {
548548 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
549549 if (builtin.zig_backend == .stage2_llvm and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest;
550550 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
551 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
552551 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
553552
554553 const S = struct {