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;...@@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
3const Target = std.Target;3const Target = std.Target;
4const log = std.log.scoped(.codegen);4const log = std.log.scoped(.codegen);
5const assert = std.debug.assert;5const assert = std.debug.assert;
6const Signedness = std.builtin.Signedness;
67
7const Module = @import("../Module.zig");8const Module = @import("../Module.zig");
8const Decl = Module.Decl;9const Decl = Module.Decl;
...@@ -423,6 +424,17 @@ const DeclGen = struct {...@@ -423,6 +424,17 @@ const DeclGen = struct {
423 return self.fail("TODO (SPIR-V): " ++ format, args);424 return self.fail("TODO (SPIR-V): " ++ format, args);
424 }425 }
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
426 /// Fetch the result-id for a previously generated instruction or constant.438 /// Fetch the result-id for a previously generated instruction or constant.
427 fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef {439 fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !IdRef {
428 const mod = self.module;440 const mod = self.module;
...@@ -631,6 +643,19 @@ const DeclGen = struct {...@@ -631,6 +643,19 @@ const DeclGen = struct {
631 const mod = self.module;643 const mod = self.module;
632 const target = self.getTarget();644 const target = self.getTarget();
633 if (ty.zigTypeTag(mod) != .Vector) return false;645 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
634 const elem_ty = ty.childType(mod);659 const elem_ty = ty.childType(mod);
635660
636 const len = ty.vectorLen(mod);661 const len = ty.vectorLen(mod);
...@@ -723,9 +748,13 @@ const DeclGen = struct {...@@ -723,9 +748,13 @@ const DeclGen = struct {
723 // Use backing bits so that negatives are sign extended748 // Use backing bits so that negatives are sign extended
724 const backing_bits = self.backingIntBits(int_info.bits).?; // Assertion failure means big int749 const backing_bits = self.backingIntBits(int_info.bits).?; // Assertion failure means big int
725750
726 const bits: u64 = switch (int_info.signedness) {751 const signedness: Signedness = switch (@typeInfo(@TypeOf(value))) {
727 // Intcast needed to silence compile errors for when the wrong path is compiled.752 .Int => |int| int.signedness,
728 // Lazy fix.753 .ComptimeInt => if (value < 0) .signed else .unsigned,
754 else => unreachable,
755 };
756
757 const bits: u64 = switch (signedness) {
729 .signed => @bitCast(@as(i64, @intCast(value))),758 .signed => @bitCast(@as(i64, @intCast(value))),
730 .unsigned => @as(u64, @intCast(value)),759 .unsigned => @as(u64, @intCast(value)),
731 };760 };
...@@ -1392,6 +1421,19 @@ const DeclGen = struct {...@@ -1392,6 +1421,19 @@ const DeclGen = struct {
1392 return ty_id;1421 return ty_id;
1393 }1422 }
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
1395 /// Generate a union type. Union types are always generated with the1437 /// Generate a union type. Union types are always generated with the
1396 /// most aligned field active. If the tag alignment is greater1438 /// most aligned field active. If the tag alignment is greater
1397 /// than that of the payload, a regular union (non-packed, with both tag and1439 /// than that of the payload, a regular union (non-packed, with both tag and
...@@ -1928,77 +1970,897 @@ const DeclGen = struct {...@@ -1928,77 +1970,897 @@ const DeclGen = struct {
1928 return union_layout;1970 return union_layout;
1929 }1971 }
19301972
1931 /// This structure is used as helper for element-wise operations. It is intended1973 /// This structure represents a "temporary" value: Something we are currently
1932 /// to be used with vectors, fake vectors (arrays) and single elements.1974 /// operating on. It typically lives no longer than the function that
1933 const WipElementWise = struct {1975 /// implements a particular AIR operation. These are used to easier
1934 dg: *DeclGen,1976 /// implement vectorizable operations (see Vectorization and the build*
1935 result_ty: Type,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.
1936 ty: Type,1983 ty: Type,
1937 /// Always in direct representation.1984 /// The value that this temporary holds. This is not necessarily
1938 ty_id: IdRef,1985 /// a value that is actually usable, or a single value: It is virtual
1939 /// True if the input is an array type.1986 /// until materialize() is called, at which point is turned into
1940 is_array: bool,1987 /// the usual SPIR-V representation of `self.ty`.
1941 /// The element-wise operation should fill these results before calling finalize().1988 value: Temporary.Value,
1942 /// These should all be in **direct** representation! `finalize()` will convert1989
1943 /// them to indirect if required.1990 const Value = union(enum) {
1944 results: []IdRef,1991 singleton: IdResult,
19451992 exploded_vector: IdRange,
1946 fn deinit(wip: *WipElementWise) void {1993 };
1947 wip.dg.gpa.free(wip.results);1994
1948 }1995 fn init(ty: Type, singleton: IdResult) Temporary {
19491996 return .{ .ty = ty, .value = .{ .singleton = singleton } };
1950 /// Utility function to extract the element at a particular index in an1997 }
1951 /// input array. This type is expected to be a fake vector (array) if `wip.is_array`, and1998
1952 /// a vector or scalar otherwise.1999 fn materialize(self: Temporary, dg: *DeclGen) !IdResult {
1953 fn elementAt(wip: WipElementWise, ty: Type, value: IdRef, index: usize) !IdRef {2000 const mod = dg.module;
1954 const mod = wip.dg.module;2001 switch (self.value) {
1955 if (wip.is_array) {2002 .singleton => |id| return id,
1956 assert(ty.isVector(mod));2003 .exploded_vector => |range| {
1957 return try wip.dg.extractVectorComponent(ty.childType(mod), value, @intCast(index));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) };
1958 } else {2094 } else {
1959 assert(index == 0);2095 return .{ .unrolled = ty.vectorLen(mod) };
1960 return value;
1961 }2096 }
1962 }2097 }
19632098
1964 /// Turns the results of this WipElementWise into a result. This can be2099 /// Given two vectorization methods, compute a "unification": a fallback
1965 /// vectors, fake vectors (arrays) and single elements, depending on `result_ty`.2100 /// that works for both, according to the following rules:
1966 /// After calling this function, this WIP is no longer usable.2101 /// - Scalars may broadcast
1967 /// Results is in `direct` representation.2102 /// - SPIR-V vectorized operations may unroll
1968 fn finalize(wip: *WipElementWise) !IdRef {2103 /// - Prefer scalar > SPIR-V vectorized > unrolled
1969 if (wip.is_array) {2104 fn unify(a: Vectorization, b: Vectorization) Vectorization {
1970 return try wip.dg.constructVector(wip.result_ty, wip.results);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 }
1971 } else {2121 } 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 }
1973 }2129 }
1974 }2130 }
19752131
1976 /// Allocate a result id at a particular index, and return it.2132 /// Force this vectorization to be unrolled, if its
1977 fn allocId(wip: *WipElementWise, index: usize) IdRef {2133 /// an operation involving vectors.
1978 assert(wip.is_array or index == 0);2134 fn unroll(self: Vectorization) Vectorization {
1979 wip.results[index] = wip.dg.spv.allocId();2135 return switch (self) {
1980 return wip.results[index];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 };
1981 }2252 }
2253
2254 /// Finalize the results of an operation back into a temporary. `results` is
2255 /// a list of result-ids of the operation.
2256 fn finalize(self: Vectorization, ty: Type, results: IdRange) Temporary {
2257 assert(self.operations() == results.len);
2258 const value: Temporary.Value = switch (self) {
2259 .scalar, .spv_vectorized => blk: {
2260 break :blk .{ .singleton = results.at(0) };
2261 },
2262 .unrolled => blk: {
2263 break :blk .{ .exploded_vector = results };
2264 },
2265 };
2266
2267 return .{ .ty = ty, .value = value };
2268 }
2269
2270 /// This struct represents an operand that has gone through some setup, and is
2271 /// ready to be used as part of an operation.
2272 const PreparedOperand = struct {
2273 ty: Type,
2274 value: PreparedOperand.Value,
2275
2276 /// The types of value that a prepared operand can hold internally. Depends
2277 /// on the operation and input value.
2278 const Value = union(enum) {
2279 /// A single scalar value that is used by a scalar operation.
2280 scalar: IdResult,
2281 /// A single scalar that is broadcasted in an unrolled operation.
2282 scalar_broadcast: IdResult,
2283 /// A SPIR-V vector that is used in SPIR-V vectorize operation.
2284 spv_vectorwise: IdResult,
2285 /// A vector represented by a consecutive list of IDs that is used in an unrolled operation.
2286 vector_exploded: IdRange,
2287 };
2288
2289 /// Query the value at a particular index of the operation. Note that
2290 /// the index is *not* the component/lane, but the index of the *operation*. When
2291 /// this operation is vectorized, the return value of this function is a SPIR-V vector.
2292 /// See also `Vectorization.operations()`.
2293 fn at(self: PreparedOperand, i: usize) IdResult {
2294 switch (self.value) {
2295 .scalar => |id| {
2296 assert(i == 0);
2297 return id;
2298 },
2299 .scalar_broadcast => |id| {
2300 return id;
2301 },
2302 .spv_vectorwise => |id| {
2303 assert(i == 0);
2304 return id;
2305 },
2306 .vector_exploded => |range| {
2307 return range.at(i);
2308 },
2309 }
2310 }
2311 };
1982 };2312 };
19832313
1984 /// Create a new element-wise operation.2314 /// A utility function to compute the vectorization style of
1985 fn elementWise(self: *DeclGen, result_ty: Type, force_element_wise: bool) !WipElementWise {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 {
1986 const mod = self.module;2341 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;2343 const dst_ty_id = try self.resolveType(dst_ty.scalarType(mod), .direct);
1993 const ty_id = try self.resolveType(ty, .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);
1995 return .{2861 return .{
1996 .dg = self,2862 v.finalize(result_ty, value_results),
1997 .result_ty = result_ty,2863 v.finalize(result_ty, overflow_results),
1998 .ty = ty,
1999 .ty_id = ty_id,
2000 .is_array = is_array,
2001 .results = results,
2002 };2864 };
2003 }2865 }
20042866
...@@ -2237,59 +3099,42 @@ const DeclGen = struct {...@@ -2237,59 +3099,42 @@ const DeclGen = struct {
2237 }3099 }
2238 }3100 }
22393101
2240 fn intFromBool(self: *DeclGen, ty: Type, condition_id: IdRef) !IdRef {3102 fn intFromBool(self: *DeclGen, value: Temporary) !Temporary {
2241 const zero_id = try self.constInt(ty, 0, .direct);3103 return try self.intFromBool2(value, Type.u1);
2242 const one_id = try self.constInt(ty, 1, .direct);3104 }
2243 const result_id = self.spv.allocId();3105
2244 try self.func.body.emit(self.spv.gpa, .OpSelect, .{3106 fn intFromBool2(self: *DeclGen, value: Temporary, result_ty: Type) !Temporary {
2245 .id_result_type = try self.resolveType(ty, .direct),3107 const zero_id = try self.constInt(result_ty, 0, .direct);
2246 .id_result = result_id,3108 const one_id = try self.constInt(result_ty, 1, .direct);
2247 .condition = condition_id,3109
2248 .object_1 = one_id,3110 return try self.buildSelect(
2249 .object_2 = zero_id,3111 value,
2250 });3112 Temporary.init(result_ty, one_id),
2251 return result_id;3113 Temporary.init(result_ty, zero_id),
3114 );
2252 }3115 }
22533116
2254 /// Convert representation from indirect (in memory) to direct (in 'register')3117 /// Convert representation from indirect (in memory) to direct (in 'register')
2255 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).3118 /// This converts the argument type from resolveType(ty, .indirect) to resolveType(ty, .direct).
2256 fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {3119 fn convertToDirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
2257 const mod = self.module;3120 const mod = self.module;
2258 const scalar_ty = ty.scalarType(mod);3121 switch (ty.scalarType(mod).zigTypeTag(mod)) {
2259 const is_spv_vector = self.isSpvVector(ty);
2260 switch (scalar_ty.zigTypeTag(mod)) {
2261 .Bool => {3122 .Bool => {
2262 // TODO: We may want to use something like elementWise in this function.3123 const false_id = try self.constBool(false, .indirect);
2263 // First we need to audit whether this would recursively call into itself.3124 // The operation below requires inputs in direct representation, but the operand
2264 if (!ty.isVector(mod) or is_spv_vector) {3125 // is actually in indirect representation.
2265 const result_id = self.spv.allocId();3126 // Cheekily swap out the type to the direct equivalent of the indirect type here, they have the
2266 const scalar_false_id = try self.constBool(false, .indirect);3127 // same representation when converted to SPIR-V.
2267 const false_id = if (is_spv_vector) blk: {3128 const operand_ty = try self.zigScalarOrVectorTypeLike(Type.u1, ty);
2268 const index = try mod.intern_pool.get(mod.gpa, .{3129 // Note: We can guarantee that these are the same ID due to the SPIR-V Module's `vector_types` cache!
2269 .vector_type = .{3130 assert(try self.resolveType(operand_ty, .direct) == try self.resolveType(ty, .indirect));
2270 .len = ty.vectorLen(mod),3131
2271 .child = Type.u1.toIntern(),3132 const result = try self.buildCmp(
2272 },3133 .i_ne,
2273 });3134 Temporary.init(operand_ty, operand_id),
2274 const vec_ty = Type.fromInterned(index);3135 Temporary.init(Type.u1, false_id),
2275 break :blk try self.constructVectorSplat(vec_ty, scalar_false_id);3136 );
2276 } else scalar_false_id;3137 return try result.materialize(self);
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);
2293 },3138 },
2294 else => return operand_id,3139 else => return operand_id,
2295 }3140 }
...@@ -2299,55 +3144,10 @@ const DeclGen = struct {...@@ -2299,55 +3144,10 @@ const DeclGen = struct {
2299 /// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect).3144 /// This converts the argument type from resolveType(ty, .direct) to resolveType(ty, .indirect).
2300 fn convertToIndirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {3145 fn convertToIndirect(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {
2301 const mod = self.module;3146 const mod = self.module;
2302 const scalar_ty = ty.scalarType(mod);3147 switch (ty.scalarType(mod).zigTypeTag(mod)) {
2303 const is_spv_vector = self.isSpvVector(ty);
2304 switch (scalar_ty.zigTypeTag(mod)) {
2305 .Bool => {3148 .Bool => {
2306 const result_ty = if (is_spv_vector) blk: {3149 const result = try self.intFromBool(Temporary.init(ty, operand_id));
2307 const index = try mod.intern_pool.get(mod.gpa, .{3150 return try result.materialize(self);
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);
2351 },3151 },
2352 else => return operand_id,3152 else => return operand_id,
2353 }3153 }
...@@ -2428,26 +3228,35 @@ const DeclGen = struct {...@@ -2428,26 +3228,35 @@ const DeclGen = struct {
2428 const air_tags = self.air.instructions.items(.tag);3228 const air_tags = self.air.instructions.items(.tag);
2429 const maybe_result_id: ?IdRef = switch (air_tags[@intFromEnum(inst)]) {3229 const maybe_result_id: ?IdRef = switch (air_tags[@intFromEnum(inst)]) {
2430 // zig fmt: off3230 // zig fmt: off
2431 .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd),3231 .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .f_add, .i_add, .i_add),
2432 .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),3232 .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .f_sub, .i_sub, .i_sub),
2433 .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),3233 .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .f_mul, .i_mul, .i_mul),
24343234
24353235 .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),
2436 .abs => try self.airAbs(inst),3244 .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,3255 .rem, .rem_optimized => try self.airArithOp(inst, .f_rem, .s_rem, .u_mod),
2442 .div_float_optimized,3256 .mod, .mod_optimized => try self.airArithOp(inst, .f_mod, .s_mod, .u_mod),
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),
24473257
24483258 .add_with_overflow => try self.airAddSubOverflow(inst, .i_add, .u_lt, .s_lt),
2449 .add_with_overflow => try self.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan),3259 .sub_with_overflow => try self.airAddSubOverflow(inst, .i_sub, .u_gt, .s_gt),
2450 .sub_with_overflow => try self.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan),
2451 .mul_with_overflow => try self.airMulOverflow(inst),3260 .mul_with_overflow => try self.airMulOverflow(inst),
2452 .shl_with_overflow => try self.airShlOverflow(inst),3261 .shl_with_overflow => try self.airShlOverflow(inst),
24533262
...@@ -2456,6 +3265,8 @@ const DeclGen = struct {...@@ -2456,6 +3265,8 @@ const DeclGen = struct {
2456 .ctz => try self.airClzCtz(inst, .ctz),3265 .ctz => try self.airClzCtz(inst, .ctz),
2457 .clz => try self.airClzCtz(inst, .clz),3266 .clz => try self.airClzCtz(inst, .clz),
24583267
3268 .select => try self.airSelect(inst),
3269
2459 .splat => try self.airSplat(inst),3270 .splat => try self.airSplat(inst),
2460 .reduce, .reduce_optimized => try self.airReduce(inst),3271 .reduce, .reduce_optimized => try self.airReduce(inst),
2461 .shuffle => try self.airShuffle(inst),3272 .shuffle => try self.airShuffle(inst),
...@@ -2463,17 +3274,17 @@ const DeclGen = struct {...@@ -2463,17 +3274,17 @@ const DeclGen = struct {
2463 .ptr_add => try self.airPtrAdd(inst),3274 .ptr_add => try self.airPtrAdd(inst),
2464 .ptr_sub => try self.airPtrSub(inst),3275 .ptr_sub => try self.airPtrSub(inst),
24653276
2466 .bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),3277 .bit_and => try self.airBinOpSimple(inst, .bit_and),
2467 .bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),3278 .bit_or => try self.airBinOpSimple(inst, .bit_or),
2468 .xor => try self.airBinOpSimple(inst, .OpBitwiseXor),3279 .xor => try self.airBinOpSimple(inst, .bit_xor),
2469 .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd),3280 .bool_and => try self.airBinOpSimple(inst, .l_and),
2470 .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr),3281 .bool_or => try self.airBinOpSimple(inst, .l_or),
24713282
2472 .shl, .shl_exact => try self.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical),3283 .shl, .shl_exact => try self.airShift(inst, .sll, .sll),
2473 .shr, .shr_exact => try self.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic),3284 .shr, .shr_exact => try self.airShift(inst, .srl, .sra),
24743285
2475 .min => try self.airMinMax(inst, .lt),3286 .min => try self.airMinMax(inst, .min),
2476 .max => try self.airMinMax(inst, .gt),3287 .max => try self.airMinMax(inst, .max),
24773288
2478 .bitcast => try self.airBitCast(inst),3289 .bitcast => try self.airBitCast(inst),
2479 .intcast, .trunc => try self.airIntCast(inst),3290 .intcast, .trunc => try self.airIntCast(inst),
...@@ -2574,39 +3385,23 @@ const DeclGen = struct {...@@ -2574,39 +3385,23 @@ const DeclGen = struct {
2574 try self.inst_results.putNoClobber(self.gpa, inst, result_id);3385 try self.inst_results.putNoClobber(self.gpa, inst, result_id);
2575 }3386 }
25763387
2577 fn binOpSimple(self: *DeclGen, ty: Type, lhs_id: IdRef, rhs_id: IdRef, comptime opcode: Opcode) !IdRef {3388 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, op: BinaryOp) !?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 {
2592 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;3389 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2593 const lhs_id = try self.resolve(bin_op.lhs);3390 const lhs = try self.temporary(bin_op.lhs);
2594 const rhs_id = try self.resolve(bin_op.rhs);3391 const rhs = try self.temporary(bin_op.rhs);
2595 const ty = self.typeOf(bin_op.lhs);
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);
2598 }3395 }
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 {
2601 const mod = self.module;3398 const mod = self.module;
2602 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;3399 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2603 const lhs_id = try self.resolve(bin_op.lhs);3400
2604 const rhs_id = try self.resolve(bin_op.rhs);3401 const base = try self.temporary(bin_op.lhs);
3402 const shift = try self.temporary(bin_op.rhs);
26053403
2606 const result_ty = self.typeOfIndex(inst);3404 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
2611 const info = self.arithmeticTypeInfo(result_ty);3406 const info = self.arithmeticTypeInfo(result_ty);
2612 switch (info.class) {3407 switch (info.class) {
...@@ -2615,121 +3410,58 @@ const DeclGen = struct {...@@ -2615,121 +3410,58 @@ const DeclGen = struct {
2615 .float, .bool => unreachable,3410 .float, .bool => unreachable,
2616 }3411 }
26173412
2618 var wip = try self.elementWise(result_ty, false);3413 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
2619 defer wip.deinit();3414 // so just manually upcast it if required.
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 };
26433415
2644 if (result_ty.isSignedInt(mod)) {3416 // Note: The sign may differ here between the shift and the base type, in case
2645 try self.func.body.emit(self.spv.gpa, signed, args);3417 // of an arithmetic right shift. SPIR-V still expects the same type,
2646 } else {3418 // so in that case we have to cast convert to signed.
2647 try self.func.body.emit(self.spv.gpa, unsigned, args);3419 const casted_shift = try self.buildIntConvert(base.ty.scalarType(mod), shift);
2648 }
26493420
2650 result_id.* = try self.normalize(wip.ty, value_id, info);3421 const shifted = switch (info.signedness) {
2651 }3422 .unsigned => try self.buildBinary(unsigned, base, casted_shift),
2652 return try wip.finalize();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);
2653 }3428 }
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 {
2656 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;3433 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);3435 const lhs = try self.temporary(bin_op.lhs);
2662 }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 {3438 const result = try self.minMax(lhs, rhs, op);
2665 const info = self.arithmeticTypeInfo(result_ty);3439 return try result.materialize(self);
2666 const target = self.getTarget();3440 }
26673441
2668 const use_backup_codegen = target.os.tag == .opencl and info.class != .float;3442 fn minMax(self: *DeclGen, lhs: Temporary, rhs: Temporary, op: MinMax) !Temporary {
2669 var wip = try self.elementWise(result_ty, use_backup_codegen);3443 const info = self.arithmeticTypeInfo(lhs.ty);
2670 defer wip.deinit();
26713444
2672 for (wip.results, 0..) |*result_id, i| {3445 const binop: BinaryOp = switch (info.class) {
2673 const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i);3446 .float => switch (op) {
2674 const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i);3447 .min => .f_min,
26753448 .max => .f_max,
2676 if (use_backup_codegen) {3449 },
2677 const cmp_id = try self.cmp(op, Type.bool, wip.ty, lhs_elem_id, rhs_elem_id);3450 .integer, .strange_integer => switch (info.signedness) {
2678 result_id.* = self.spv.allocId();3451 .signed => switch (op) {
2679 try self.func.body.emit(self.spv.gpa, .OpSelect, .{3452 .min => .s_min,
2680 .id_result_type = wip.ty_id,3453 .max => .s_max,
2681 .id_result = result_id.*,3454 },
2682 .condition = cmp_id,3455 .unsigned => switch (op) {
2683 .object_1 = lhs_elem_id,3456 .min => .u_min,
2684 .object_2 = rhs_elem_id,3457 .max => .u_max,
2685 });3458 },
2686 } else {3459 },
2687 const ext_inst: Word = switch (target.os.tag) {3460 .composite_integer => unreachable, // TODO
2688 .opencl => switch (op) {3461 .bool => unreachable,
2689 .lt => 28, // fmin3462 };
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 };
27213463
2722 result_id.* = self.spv.allocId();3464 return try self.buildBinary(binop, lhs, rhs);
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();
2733 }3465 }
27343466
2735 /// This function normalizes values to a canonical representation3467 /// This function normalizes values to a canonical representation
...@@ -2740,41 +3472,24 @@ const DeclGen = struct {...@@ -2740,41 +3472,24 @@ const DeclGen = struct {
2740 /// - Signed integers are also sign extended if they are negative.3472 /// - Signed integers are also sign extended if they are negative.
2741 /// All other values are returned unmodified (this makes strange integer3473 /// All other values are returned unmodified (this makes strange integer
2742 /// wrapping easier to use in generic operations).3474 /// 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;
2744 switch (info.class) {3478 switch (info.class) {
2745 .integer, .bool, .float => return value_id,3479 .integer, .bool, .float => return value,
2746 .composite_integer => unreachable, // TODO3480 .composite_integer => unreachable, // TODO
2747 .strange_integer => switch (info.signedness) {3481 .strange_integer => switch (info.signedness) {
2748 .unsigned => {3482 .unsigned => {
2749 const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1;3483 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();3484 const mask_id = try self.constInt(ty.scalarType(mod), mask_value, .direct);
2751 const mask_id = try self.constInt(ty, mask_value, .direct);3485 return try self.buildBinary(.bit_and, value, Temporary.init(ty.scalarType(mod), mask_id));
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;
2759 },3486 },
2760 .signed => {3487 .signed => {
2761 // Shift left and right so that we can copy the sight bit that way.3488 // 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);3489 const shift_amt_id = try self.constInt(ty.scalarType(mod), info.backing_bits - info.bits, .direct);
2763 const left_id = self.spv.allocId();3490 const shift_amt = Temporary.init(ty.scalarType(mod), shift_amt_id);
2764 try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{3491 const left = try self.buildBinary(.sll, value, shift_amt);
2765 .id_result_type = try self.resolveType(ty, .direct),3492 return try self.buildBinary(.sra, left, shift_amt);
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;
2778 },3493 },
2779 },3494 },
2780 }3495 }
...@@ -2782,491 +3497,438 @@ const DeclGen = struct {...@@ -2782,491 +3497,438 @@ const DeclGen = struct {
27823497
2783 fn airDivFloor(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3498 fn airDivFloor(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2784 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;3499 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2785 const lhs_id = try self.resolve(bin_op.lhs);3500
2786 const rhs_id = try self.resolve(bin_op.rhs);3501 const lhs = try self.temporary(bin_op.lhs);
2787 const ty = self.typeOfIndex(inst);3502 const rhs = try self.temporary(bin_op.rhs);
2788 const ty_id = try self.resolveType(ty, .direct);3503
2789 const info = self.arithmeticTypeInfo(ty);3504 const info = self.arithmeticTypeInfo(lhs.ty);
2790 switch (info.class) {3505 switch (info.class) {
2791 .composite_integer => unreachable, // TODO3506 .composite_integer => unreachable, // TODO
2792 .integer, .strange_integer => {3507 .integer, .strange_integer => {
2793 const zero_id = try self.constInt(ty, 0, .direct);3508 switch (info.signedness) {
2794 const one_id = try self.constInt(ty, 1, .direct);3509 .unsigned => {
27953510 const result = try self.buildBinary(.u_div, lhs, rhs);
2796 // (a ^ b) > 03511 return try result.materialize(self);
2797 const bin_bitwise_id = try self.binOpSimple(ty, lhs_id, rhs_id, .OpBitwiseXor);3512 },
2798 const is_positive_id = try self.cmp(.gt, Type.bool, ty, bin_bitwise_id, zero_id);3513 .signed => {},
27993514 }
2800 // a / b3515
2801 const positive_div_id = try self.arithOp(ty, lhs_id, rhs_id, .OpFDiv, .OpSDiv, .OpUDiv);3516 // For signed integers:
28023517 // (a / b) - (a % b != 0 && a < 0 != b < 0);
2803 // - (abs(a) + abs(b) - 1) / abs(b)3518 // There shouldn't be any overflow issues.
2804 const lhs_abs = try self.abs(ty, ty, lhs_id);3519
2805 const rhs_abs = try self.abs(ty, ty, rhs_id);3520 const div = try self.buildBinary(.s_div, lhs, rhs);
2806 const negative_div_lhs = try self.arithOp(3521 const rem = try self.buildBinary(.s_rem, lhs, rhs);
2807 ty,3522
2808 try self.arithOp(ty, lhs_abs, rhs_abs, .OpFAdd, .OpIAdd, .OpIAdd),3523 const zero = Temporary.init(lhs.ty, try self.constInt(lhs.ty, 0, .direct));
2809 one_id,3524
2810 .OpFSub,3525 const rem_is_not_zero = try self.buildCmp(.i_ne, rem, zero);
2811 .OpISub,3526
2812 .OpISub,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,
2813 );3536 );
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();3538 const result = try self.buildBinary(
2823 try self.func.body.emit(self.spv.gpa, .OpSelect, .{3539 .i_sub,
2824 .id_result_type = ty_id,3540 div,
2825 .id_result = result_id,3541 try self.intFromBool2(rem_is_not_zero_and_result_is_negative, div.ty),
2826 .condition = is_positive_id,3542 );
2827 .object_1 = positive_div_id,3543
2828 .object_2 = negated_negative_div_id,3544 return try result.materialize(self);
2829 });
2830 return result_id;
2831 },3545 },
2832 .float => {3546 .float => {
2833 const div_id = try self.arithOp(ty, lhs_id, rhs_id, .OpFDiv, .OpSDiv, .OpUDiv);3547 const div = try self.buildBinary(.f_div, lhs, rhs);
2834 return try self.floor(ty, div_id);3548 const result = try self.buildUnary(.floor, div);
3549 return try result.materialize(self);
2835 },3550 },
2836 .bool => unreachable,3551 .bool => unreachable,
2837 }3552 }
2838 }3553 }
28393554
2840 fn airFloor(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3555 fn airDivTrunc(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2841 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;3556 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_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 }
28463557
2847 fn floor(self: *DeclGen, ty: Type, operand_id: IdRef) !IdRef {3558 const lhs = try self.temporary(bin_op.lhs);
2848 const target = self.getTarget();3559 const rhs = try self.temporary(bin_op.rhs);
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 };
28603560
2861 const result_id = self.spv.allocId();3561 const info = self.arithmeticTypeInfo(lhs.ty);
2862 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{3562 switch (info.class) {
2863 .id_result_type = ty_id,3563 .composite_integer => unreachable, // TODO
2864 .id_result = result_id,3564 .integer, .strange_integer => switch (info.signedness) {
2865 .set = set_id,3565 .unsigned => {
2866 .instruction = .{ .inst = ext_inst },3566 const result = try self.buildBinary(.u_div, lhs, rhs);
2867 .id_ref_4 = &.{operand_id},3567 return try result.materialize(self);
2868 });3568 },
2869 return result_id;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);
2870 }3588 }
28713589
2872 fn airArithOp(3590 fn airArithOp(
2873 self: *DeclGen,3591 self: *DeclGen,
2874 inst: Air.Inst.Index,3592 inst: Air.Inst.Index,
2875 comptime fop: Opcode,3593 comptime fop: BinaryOp,
2876 comptime sop: Opcode,3594 comptime sop: BinaryOp,
2877 comptime uop: Opcode,3595 comptime uop: BinaryOp,
2878 ) !?IdRef {3596 ) !?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);
2882 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;3597 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);3599 const lhs = try self.temporary(bin_op.lhs);
2890 }3600 const rhs = try self.temporary(bin_op.rhs);
28913601
2892 fn arithOp(3602 const info = self.arithmeticTypeInfo(lhs.ty);
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);
29043603
2905 const opcode_index: usize = switch (info.class) {3604 const result = switch (info.class) {
2906 .composite_integer => {3605 .composite_integer => unreachable, // TODO
2907 return self.todo("binary operations for composite integers", .{});
2908 },
2909 .integer, .strange_integer => switch (info.signedness) {3606 .integer, .strange_integer => switch (info.signedness) {
2910 .signed => 1,3607 .signed => try self.buildBinary(sop, lhs, rhs),
2911 .unsigned => 2,3608 .unsigned => try self.buildBinary(uop, lhs, rhs),
2912 },3609 },
2913 .float => 0,3610 .float => try self.buildBinary(fop, lhs, rhs),
2914 .bool => unreachable,3611 .bool => unreachable,
2915 };3612 };
29163613
2917 var wip = try self.elementWise(ty, false);3614 return try result.materialize(self);
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();
2944 }3615 }
29453616
2946 fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3617 fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
2947 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;3618 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);
2949 // Note: operand_ty may be signed, while ty is always unsigned!3620 // Note: operand_ty may be signed, while ty is always unsigned!
2950 const operand_ty = self.typeOf(ty_op.operand);
2951 const result_ty = self.typeOfIndex(inst);3621 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);
2953 }3624 }
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 {
2956 const target = self.getTarget();3627 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);3630 switch (operand_info.class) {
2960 defer wip.deinit();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| {3635 // TODO: We may need to bitcast the result to a uint
2963 const elem_id = try wip.elementAt(operand_ty, operand_id, i);3636 // depending on the result type. Do that when
29643637 // bitCast is implemented for vectors.
2965 const ext_inst: Word = switch (target.os.tag) {3638 // This is only relevant for Vulkan
2966 .opencl => switch (operand_info.class) {3639 assert(target.os.tag != .vulkan); // TODO
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 };
29883640
2989 result_id.* = self.spv.allocId();3641 return try self.normalize(abs_value, self.arithmeticTypeInfo(result_ty));
2990 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{3642 },
2991 .id_result_type = wip.ty_id,3643 .composite_integer => unreachable, // TODO
2992 .id_result = result_id.*,3644 .bool => unreachable,
2993 .set = set_id,
2994 .instruction = .{ .inst = ext_inst },
2995 .id_ref_4 = &.{elem_id},
2996 });
2997 }3645 }
2998 return try wip.finalize();
2999 }3646 }
30003647
3001 fn airAddSubOverflow(3648 fn airAddSubOverflow(
3002 self: *DeclGen,3649 self: *DeclGen,
3003 inst: Air.Inst.Index,3650 inst: Air.Inst.Index,
3004 comptime add: Opcode,3651 comptime add: BinaryOp,
3005 comptime ucmp: Opcode,3652 comptime ucmp: CmpPredicate,
3006 comptime scmp: Opcode,3653 comptime scmp: CmpPredicate,
3007 ) !?IdRef {3654 ) !?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
3009 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;3661 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3010 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;3662 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);3664 const lhs = try self.temporary(extra.lhs);
3015 const operand_ty = self.typeOf(extra.lhs);3665 const rhs = try self.temporary(extra.rhs);
3016 const ov_ty = result_ty.structFieldType(1, self.module);
30173666
3018 const bool_ty_id = try self.resolveType(Type.bool, .direct);3667 const result_ty = self.typeOfIndex(inst);
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;
30243668
3025 const info = self.arithmeticTypeInfo(operand_ty);3669 const info = self.arithmeticTypeInfo(lhs.ty);
3026 switch (info.class) {3670 switch (info.class) {
3027 .composite_integer => return self.todo("overflow ops for composite integers", .{}),3671 .composite_integer => unreachable, // TODO
3028 .strange_integer, .integer => {},3672 .strange_integer, .integer => {},
3029 .float, .bool => unreachable,3673 .float, .bool => unreachable,
3030 }3674 }
30313675
3032 var wip_result = try self.elementWise(operand_ty, false);3676 const sum = try self.buildBinary(add, lhs, rhs);
3033 defer wip_result.deinit();3677 const result = try self.normalize(sum, info);
3034 var wip_ov = try self.elementWise(ov_ty, false);3678
3035 defer wip_ov.deinit();3679 const overflowed = switch (info.signedness) {
3036 for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| {3680 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.
3037 const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i);3681 // For subtraction the conditions need to be swapped.
3038 const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i);3682 .unsigned => try self.buildCmp(ucmp, result, lhs),
30393683 // For addition, overflow happened if:
3040 // Normalize both so that we can properly check for overflow3684 // - rhs is negative and value > lhs
3041 const value_id = self.spv.allocId();3685 // - rhs is positive and value < lhs
30423686 // This can be shortened to:
3043 try self.func.body.emit(self.spv.gpa, add, .{3687 // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs)
3044 .id_result_type = wip_result.ty_id,3688 // = (rhs < 0) == (value > lhs)
3045 .id_result = value_id,3689 // = (rhs < 0) == (lhs < value)
3046 .operand_1 = lhs_elem_id,3690 // Note that signed overflow is also wrapping in spir-v.
3047 .operand_2 = rhs_elem_id,3691 // For subtraction, overflow happened if:
3048 });3692 // - rhs is negative and value < lhs
30493693 // - rhs is positive and value > lhs
3050 // Normalize the result so that the comparisons go well3694 // This can be shortened to:
3051 result_id.* = try self.normalize(wip_result.ty, value_id, info);3695 // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs)
30523696 // = (rhs < 0) == (value < lhs)
3053 const overflowed_id = switch (info.signedness) {3697 // = (rhs < 0) == (lhs > value)
3054 .unsigned => blk: {3698 .signed => blk: {
3055 // Overflow happened if the result is smaller than either of the operands. It doesn't matter which.3699 const zero = Temporary.init(rhs.ty, try self.constInt(rhs.ty, 0, .direct));
3056 // For subtraction the conditions need to be swapped.3700 const rhs_lt_zero = try self.buildCmp(.s_lt, rhs, zero);
3057 const overflowed_id = self.spv.allocId();3701 const result_gt_lhs = try self.buildCmp(scmp, lhs, result);
3058 try self.func.body.emit(self.spv.gpa, ucmp, .{3702 break :blk try self.buildCmp(.l_eq, rhs_lt_zero, result_gt_lhs);
3059 .id_result_type = cmp_ty_id,3703 },
3060 .id_result = overflowed_id,3704 };
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 };
31113705
3112 ov_id.* = try self.intFromBool(wip_ov.ty, overflowed_id);3706 const ov = try self.intFromBool(overflowed);
3113 }
31143707
3115 return try self.constructStruct(3708 return try self.constructStruct(
3116 result_ty,3709 result_ty,
3117 &.{ operand_ty, ov_ty },3710 &.{ result.ty, ov.ty },
3118 &.{ try wip_result.finalize(), try wip_ov.finalize() },3711 &.{ try result.materialize(self), try ov.materialize(self) },
3119 );3712 );
3120 }3713 }
31213714
3122 fn airMulOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3715 fn airMulOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3716 const target = self.getTarget();
3717 const mod = self.module;
3718
3123 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;3719 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3124 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;3720 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3125 const lhs = try self.resolve(extra.lhs);3721
3126 const rhs = try self.resolve(extra.rhs);3722 const lhs = try self.temporary(extra.lhs);
3723 const rhs = try self.temporary(extra.rhs);
31273724
3128 const result_ty = self.typeOfIndex(inst);3725 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);
3133 switch (info.class) {3728 switch (info.class) {
3134 .composite_integer => return self.todo("overflow ops for composite integers", .{}),3729 .composite_integer => unreachable, // TODO
3135 .strange_integer, .integer => {},3730 .strange_integer, .integer => {},
3136 .float, .bool => unreachable,3731 .float, .bool => unreachable,
3137 }3732 }
31383733
3139 var wip_result = try self.elementWise(operand_ty, true);3734 // There are 3 cases which we have to deal with:
3140 defer wip_result.deinit();3735 // - If info.bits < 32 / 2, we will upcast to 32 and check the higher bits
3141 var wip_ov = try self.elementWise(ov_ty, true);3736 // - If info.bits > 32 / 2, we have to use extended multiplication
3142 defer wip_ov.deinit();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);3751 const result, const overflowed = switch (info.signedness) {
3145 const zero_ov_id = try self.constInt(wip_ov.ty, 0, .direct);3752 .unsigned => blk: {
3146 const one_ov_id = try self.constInt(wip_ov.ty, 1, .direct);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| {3758 const full_result = try self.buildBinary(.i_mul, casted_lhs, casted_rhs);
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);
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)3763 // Shift the result bits away to get the overflow bits.
3155 const not_zero_id = try self.cmp(.neq, Type.bool, wip_result.ty, lhs_elem_id, zero_id);3764 const shift = Temporary.init(full_result.ty, try self.constInt(full_result.ty, info.bits, .direct));
3156 const res_rhs_id = try self.arithOp(wip_result.ty, result_id.*, lhs_elem_id, .OpFDiv, .OpSDiv, .OpUDiv);3765 const overflow = try self.buildBinary(.srl, full_result, shift);
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);
31593766
3160 ov_id.* = self.spv.allocId();3767 // Directly check if its zero in the op_ty without converting first.
3161 try self.func.body.emit(self.spv.gpa, .OpSelect, .{3768 const zero = Temporary.init(full_result.ty, try self.constInt(full_result.ty, 0, .direct));
3162 .id_result_type = wip_ov.ty_id,3769 const overflowed = try self.buildCmp(.i_ne, zero, overflow);
3163 .id_result = ov_id.*,3770
3164 .condition = cond_id,3771 break :blk .{ result, overflowed };
3165 .object_1 = one_ov_id,3772 }
3166 .object_2 = zero_ov_id,3773
3167 });3774 const low_bits, const high_bits = try self.buildWideMul(.u_mul_extended, lhs, rhs);
3168 }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
3170 return try self.constructStruct(3888 return try self.constructStruct(
3171 result_ty,3889 result_ty,
3172 &.{ operand_ty, ov_ty },3890 &.{ result.ty, ov.ty },
3173 &.{ try wip_result.finalize(), try wip_ov.finalize() },3891 &.{ try result.materialize(self), try ov.materialize(self) },
3174 );3892 );
3175 }3893 }
31763894
3177 fn airShlOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3895 fn airShlOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3178 const mod = self.module;3896 const mod = self.module;
3897
3179 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;3898 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3180 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;3899 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);3904 const result_ty = self.typeOfIndex(inst);
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;
31983905
3199 const info = self.arithmeticTypeInfo(operand_ty);3906 const info = self.arithmeticTypeInfo(base.ty);
3200 switch (info.class) {3907 switch (info.class) {
3201 .composite_integer => return self.todo("overflow shift for composite integers", .{}),3908 .composite_integer => unreachable, // TODO
3202 .integer, .strange_integer => {},3909 .integer, .strange_integer => {},
3203 .float, .bool => unreachable,3910 .float, .bool => unreachable,
3204 }3911 }
32053912
3206 var wip_result = try self.elementWise(operand_ty, false);3913 // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that,
3207 defer wip_result.deinit();3914 // so just manually upcast it if required.
3208 var wip_ov = try self.elementWise(ov_ty, false);3915 const casted_shift = try self.buildIntConvert(base.ty.scalarType(mod), shift);
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);
32343916
3235 const right_shift_id = self.spv.allocId();3917 const left = try self.buildBinary(.sll, base, casted_shift);
3236 switch (info.signedness) {3918 const result = try self.normalize(left, info);
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 }
32543919
3255 const overflowed_id = self.spv.allocId();3920 const right = switch (info.signedness) {
3256 try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{3921 .unsigned => try self.buildBinary(.srl, result, casted_shift),
3257 .id_result_type = cmp_ty_id,3922 .signed => try self.buildBinary(.sra, result, casted_shift),
3258 .id_result = overflowed_id,3923 };
3259 .operand_1 = lhs_elem_id,
3260 .operand_2 = right_shift_id,
3261 });
32623924
3263 ov_id.* = try self.intFromBool(wip_ov.ty, overflowed_id);3925 const overflowed = try self.buildCmp(.i_ne, base, right);
3264 }3926 const ov = try self.intFromBool(overflowed);
32653927
3266 return try self.constructStruct(3928 return try self.constructStruct(
3267 result_ty,3929 result_ty,
3268 &.{ operand_ty, ov_ty },3930 &.{ result.ty, ov.ty },
3269 &.{ try wip_result.finalize(), try wip_ov.finalize() },3931 &.{ try result.materialize(self), try ov.materialize(self) },
3270 );3932 );
3271 }3933 }
32723934
...@@ -3274,122 +3936,67 @@ const DeclGen = struct {...@@ -3274,122 +3936,67 @@ const DeclGen = struct {
3274 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;3936 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
3275 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;3937 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
32763938
3277 const mulend1 = try self.resolve(extra.lhs);3939 const a = try self.temporary(extra.lhs);
3278 const mulend2 = try self.resolve(extra.rhs);3940 const b = try self.temporary(extra.rhs);
3279 const addend = try self.resolve(pl_op.operand);3941 const c = try self.temporary(pl_op.operand);
3280
3281 const ty = self.typeOfIndex(inst);
32823942
3283 const info = self.arithmeticTypeInfo(ty);3943 const result_ty = self.typeOfIndex(inst);
3944 const info = self.arithmeticTypeInfo(result_ty);
3284 assert(info.class == .float); // .mul_add is only emitted for floats3945 assert(info.class == .float); // .mul_add is only emitted for floats
32853946
3286 var wip = try self.elementWise(ty, false);3947 const result = try self.buildFma(a, b, c);
3287 defer wip.deinit();3948 return try result.materialize(self);
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();
3305 }3949 }
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 {
3308 if (self.liveness.isUnused(inst)) return null;3952 if (self.liveness.isUnused(inst)) return null;
33093953
3310 const mod = self.module;3954 const mod = self.module;
3311 const target = self.getTarget();3955 const target = self.getTarget();
3312 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;3956 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3313 const result_ty = self.typeOfIndex(inst);3957 const operand = try self.temporary(ty_op.operand);
3314 const operand_ty = self.typeOf(ty_op.operand);
3315 const operand = try self.resolve(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);
3318 switch (info.class) {3962 switch (info.class) {
3319 .composite_integer => unreachable, // TODO3963 .composite_integer => unreachable, // TODO
3320 .integer, .strange_integer => {},3964 .integer, .strange_integer => {},
3321 .float, .bool => unreachable,3965 .float, .bool => unreachable,
3322 }3966 }
33233967
3324 var wip = try self.elementWise(result_ty, false);3968 switch (target.os.tag) {
3325 defer wip.deinit();3969 .vulkan => unreachable, // TODO
33263970 else => {},
3327 const elem_ty = if (wip.is_array) operand_ty.scalarType(mod) else operand_ty;3971 }
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 };
33403972
3341 // Note: result of OpenCL ctz/clz returns operand_ty, and we want result_ty.3973 const count = try self.buildUnary(op, operand);
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 });
33523974
3353 // TODO: Comparison should be removed..3975 // Result of OpenCL ctz/clz returns operand.ty, and we want result_ty.
3354 // Its valid because SpvModule caches numeric types3976 // result_ty is always large enough to hold the result, so we might have to down
3355 if (wip.ty_id == elem_ty_id) {3977 // cast it.
3356 result_id.* = tmp;3978 const result = try self.buildIntConvert(scalar_result_ty, count);
3357 continue;3979 return try result.materialize(self);
3358 }3980 }
33593981
3360 result_id.* = self.spv.allocId();3982 fn airSelect(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3361 if (result_ty.scalarType(mod).isSignedInt(mod)) {3983 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
3362 assert(elem_ty.scalarType(mod).isSignedInt(mod));3984 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
3363 try self.func.body.emit(self.spv.gpa, .OpSConvert, .{3985 const pred = try self.temporary(pl_op.operand);
3364 .id_result_type = wip.ty_id,3986 const a = try self.temporary(extra.lhs);
3365 .id_result = result_id.*,3987 const b = try self.temporary(extra.rhs);
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 }
33813988
3382 return try wip.finalize();3989 const result = try self.buildSelect(pred, a, b);
3990 return try result.materialize(self);
3383 }3991 }
33843992
3385 fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3993 fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3386 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;3994 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3995
3387 const operand_id = try self.resolve(ty_op.operand);3996 const operand_id = try self.resolve(ty_op.operand);
3388 const result_ty = self.typeOfIndex(inst);3997 const result_ty = self.typeOfIndex(inst);
3389 var wip = try self.elementWise(result_ty, true);3998
3390 defer wip.deinit();3999 return try self.constructVectorSplat(result_ty, operand_id);
3391 @memset(wip.results, operand_id);
3392 return try wip.finalize();
3393 }4000 }
33944001
3395 fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4002 fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
...@@ -3402,23 +4009,33 @@ const DeclGen = struct {...@@ -3402,23 +4009,33 @@ const DeclGen = struct {
34024009
3403 const info = self.arithmeticTypeInfo(operand_ty);4010 const info = self.arithmeticTypeInfo(operand_ty);
34044011
3405 var result_id = try self.extractVectorComponent(scalar_ty, operand, 0);
3406 const len = operand_ty.vectorLen(mod);4012 const len = operand_ty.vectorLen(mod);
34074013
4014 const first = try self.extractVectorComponent(scalar_ty, operand, 0);
4015
3408 switch (reduce.operation) {4016 switch (reduce.operation) {
3409 .Min, .Max => |op| {4017 .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 };
3411 for (1..len) |i| {4024 for (1..len) |i| {
3412 const lhs = result_id;4025 const lhs = result;
3413 const rhs = try self.extractVectorComponent(scalar_ty, operand, @intCast(i));4026 const rhs_id = try self.extractVectorComponent(scalar_ty, operand, @intCast(i));
3414 result_id = try self.minMax(scalar_ty, cmp_op, lhs, rhs);4027 const rhs = Temporary.init(scalar_ty, rhs_id);
4028
4029 result = try self.minMax(lhs, rhs, cmp_op);
3415 }4030 }
34164031
3417 return result_id;4032 return try result.materialize(self);
3418 },4033 },
3419 else => {},4034 else => {},
3420 }4035 }
34214036
4037 var result_id = first;
4038
3422 const opcode: Opcode = switch (info.class) {4039 const opcode: Opcode = switch (info.class) {
3423 .bool => switch (reduce.operation) {4040 .bool => switch (reduce.operation) {
3424 .And => .OpLogicalAnd,4041 .And => .OpLogicalAnd,
...@@ -3602,50 +4219,66 @@ const DeclGen = struct {...@@ -3602,50 +4219,66 @@ const DeclGen = struct {
3602 fn cmp(4219 fn cmp(
3603 self: *DeclGen,4220 self: *DeclGen,
3604 op: std.math.CompareOperator,4221 op: std.math.CompareOperator,
3605 result_ty: Type,4222 lhs: Temporary,
3606 ty: Type,4223 rhs: Temporary,
3607 lhs_id: IdRef,4224 ) !Temporary {
3608 rhs_id: IdRef,
3609 ) !IdRef {
3610 const mod = self.module;4225 const mod = self.module;
3611 var cmp_lhs_id = lhs_id;4226 const scalar_ty = lhs.ty.scalarType(mod);
3612 var cmp_rhs_id = rhs_id;4227 const is_vector = lhs.ty.isVector(mod);
3613 const bool_ty_id = try self.resolveType(Type.bool, .direct);4228
3614 const op_ty = switch (ty.zigTypeTag(mod)) {4229 switch (scalar_ty.zigTypeTag(mod)) {
3615 .Int, .Bool, .Float => ty,4230 .Int, .Bool, .Float => {},
3616 .Enum => ty.intTagType(mod),4231 .Enum => {
3617 .ErrorSet => Type.u16,4232 assert(!is_vector);
3618 .Pointer => blk: {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);
3619 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are4242 // Note that while SPIR-V offers OpPtrEqual and OpPtrNotEqual, they are
3620 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using4243 // currently not implemented in the SPIR-V LLVM translator. Thus, we emit these using
3621 // OpConvertPtrToU...4244 // OpConvertPtrToU...
3622 cmp_lhs_id = self.spv.allocId();
3623 cmp_rhs_id = self.spv.allocId();
36244245
3625 const usize_ty_id = try self.resolveType(Type.usize, .direct);4246 const usize_ty_id = try self.resolveType(Type.usize, .direct);
36264247
4248 const lhs_int_id = self.spv.allocId();
3627 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{4249 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
3628 .id_result_type = usize_ty_id,4250 .id_result_type = usize_ty_id,
3629 .id_result = cmp_lhs_id,4251 .id_result = lhs_int_id,
3630 .pointer = lhs_id,4252 .pointer = try lhs.materialize(self),
3631 });4253 });
36324254
4255 const rhs_int_id = self.spv.allocId();
3633 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{4256 try self.func.body.emit(self.spv.gpa, .OpConvertPtrToU, .{
3634 .id_result_type = usize_ty_id,4257 .id_result_type = usize_ty_id,
3635 .id_result = cmp_rhs_id,4258 .id_result = rhs_int_id,
3636 .pointer = rhs_id,4259 .pointer = try rhs.materialize(self),
3637 });4260 });
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);
3640 },4265 },
3641 .Optional => {4266 .Optional => {
4267 assert(!is_vector);
4268
4269 const ty = lhs.ty;
4270
3642 const payload_ty = ty.optionalChild(mod);4271 const payload_ty = ty.optionalChild(mod);
3643 if (ty.optionalReprIsPayload(mod)) {4272 if (ty.optionalReprIsPayload(mod)) {
3644 assert(payload_ty.hasRuntimeBitsIgnoreComptime(mod));4273 assert(payload_ty.hasRuntimeBitsIgnoreComptime(mod));
3645 assert(!payload_ty.isSlice(mod));4274 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));
3647 }4277 }
36484278
4279 const lhs_id = try lhs.materialize(self);
4280 const rhs_id = try rhs.materialize(self);
4281
3649 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod))4282 const lhs_valid_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod))
3650 try self.extractField(Type.bool, lhs_id, 1)4283 try self.extractField(Type.bool, lhs_id, 1)
3651 else4284 else
...@@ -3656,8 +4289,11 @@ const DeclGen = struct {...@@ -3656,8 +4289,11 @@ const DeclGen = struct {
3656 else4289 else
3657 try self.convertToDirect(Type.bool, rhs_id);4290 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
3659 if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {4295 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);
3661 }4297 }
36624298
3663 // a = lhs_valid4299 // a = lhs_valid
...@@ -3678,118 +4314,71 @@ const DeclGen = struct {...@@ -3678,118 +4314,71 @@ const DeclGen = struct {
3678 const lhs_pl_id = try self.extractField(payload_ty, lhs_id, 0);4314 const lhs_pl_id = try self.extractField(payload_ty, lhs_id, 0);
3679 const rhs_pl_id = try self.extractField(payload_ty, rhs_id, 0);4315 const rhs_pl_id = try self.extractField(payload_ty, rhs_id, 0);
36804316
3681 switch (op) {4317 const lhs_pl = Temporary.init(payload_ty, lhs_pl_id);
3682 .eq => {4318 const rhs_pl = Temporary.init(payload_ty, rhs_pl_id);
3683 const valid_eq_id = try self.cmp(.eq, Type.bool, Type.bool, lhs_valid_id, rhs_valid_id);4319
3684 const pl_eq_id = try self.cmp(op, Type.bool, payload_ty, lhs_pl_id, rhs_pl_id);4320 return switch (op) {
3685 const lhs_not_valid_id = self.spv.allocId();4321 .eq => try self.buildBinary(
3686 try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{4322 .l_and,
3687 .id_result_type = bool_ty_id,4323 try self.cmp(.eq, lhs_valid, rhs_valid),
3688 .id_result = lhs_not_valid_id,4324 try self.buildBinary(
3689 .operand = lhs_valid_id,4325 .l_or,
3690 });4326 try self.buildUnary(.l_not, lhs_valid),
3691 const impl_id = self.spv.allocId();4327 try self.cmp(.eq, lhs_pl, rhs_pl),
3692 try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{4328 ),
3693 .id_result_type = bool_ty_id,4329 ),
3694 .id_result = impl_id,4330 .neq => try self.buildBinary(
3695 .operand_1 = lhs_not_valid_id,4331 .l_or,
3696 .operand_2 = pl_eq_id,4332 try self.cmp(.neq, lhs_valid, rhs_valid),
3697 });4333 try self.buildBinary(
3698 const result_id = self.spv.allocId();4334 .l_and,
3699 try self.func.body.emit(self.spv.gpa, .OpLogicalAnd, .{4335 lhs_valid,
3700 .id_result_type = bool_ty_id,4336 try self.cmp(.neq, lhs_pl, rhs_pl),
3701 .id_result = result_id,4337 ),
3702 .operand_1 = valid_eq_id,4338 ),
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 },
3727 else => unreachable,4339 else => unreachable,
3728 }4340 };
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();
3740 },4341 },
3741 else => unreachable,4342 else => unreachable,
3742 };4343 }
37434344
3744 const opcode: Opcode = opcode: {4345 const info = self.arithmeticTypeInfo(scalar_ty);
3745 const info = self.arithmeticTypeInfo(op_ty);4346 const pred: CmpPredicate = switch (info.class) {
3746 const signedness = switch (info.class) {4347 .composite_integer => unreachable, // TODO
3747 .composite_integer => {4348 .float => switch (op) {
3748 return self.todo("binary operations for composite integers", .{});4349 .eq => .f_oeq,
3749 },4350 .neq => .f_une,
3750 .float => break :opcode switch (op) {4351 .lt => .f_olt,
3751 .eq => .OpFOrdEqual,4352 .lte => .f_ole,
3752 .neq => .OpFUnordNotEqual,4353 .gt => .f_ogt,
3753 .lt => .OpFOrdLessThan,4354 .gte => .f_oge,
3754 .lte => .OpFOrdLessThanEqual,4355 },
3755 .gt => .OpFOrdGreaterThan,4356 .bool => switch (op) {
3756 .gte => .OpFOrdGreaterThanEqual,4357 .eq => .l_eq,
3757 },4358 .neq => .l_ne,
3758 .bool => break :opcode switch (op) {4359 else => unreachable,
3759 .eq => .OpLogicalEqual,4360 },
3760 .neq => .OpLogicalNotEqual,4361 .integer, .strange_integer => switch (info.signedness) {
3761 else => unreachable,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,
3762 },4369 },
3763 .integer, .strange_integer => info.signedness,
3764 };
3765
3766 break :opcode switch (signedness) {
3767 .unsigned => switch (op) {4370 .unsigned => switch (op) {
3768 .eq => .OpIEqual,4371 .eq => .i_eq,
3769 .neq => .OpINotEqual,4372 .neq => .i_ne,
3770 .lt => .OpULessThan,4373 .lt => .u_lt,
3771 .lte => .OpULessThanEqual,4374 .lte => .u_le,
3772 .gt => .OpUGreaterThan,4375 .gt => .u_gt,
3773 .gte => .OpUGreaterThanEqual,4376 .gte => .u_ge,
3774 },
3775 .signed => switch (op) {
3776 .eq => .OpIEqual,
3777 .neq => .OpINotEqual,
3778 .lt => .OpSLessThan,
3779 .lte => .OpSLessThanEqual,
3780 .gt => .OpSGreaterThan,
3781 .gte => .OpSGreaterThanEqual,
3782 },4377 },
3783 };4378 },
3784 };4379 };
37854380
3786 const result_id = self.spv.allocId();4381 return try self.buildCmp(pred, lhs, rhs);
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;
3793 }4382 }
37944383
3795 fn airCmp(4384 fn airCmp(
...@@ -3798,24 +4387,22 @@ const DeclGen = struct {...@@ -3798,24 +4387,22 @@ const DeclGen = struct {
3798 comptime op: std.math.CompareOperator,4387 comptime op: std.math.CompareOperator,
3799 ) !?IdRef {4388 ) !?IdRef {
3800 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;4389 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
3801 const lhs_id = try self.resolve(bin_op.lhs);4390 const lhs = try self.temporary(bin_op.lhs);
3802 const rhs_id = try self.resolve(bin_op.rhs);4391 const rhs = try self.temporary(bin_op.rhs);
3803 const ty = self.typeOf(bin_op.lhs);
3804 const result_ty = self.typeOfIndex(inst);
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);
3807 }4395 }
38084396
3809 fn airVectorCmp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4397 fn airVectorCmp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3810 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;4398 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3811 const vec_cmp = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;4399 const vec_cmp = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;
3812 const lhs_id = try self.resolve(vec_cmp.lhs);4400 const lhs = try self.temporary(vec_cmp.lhs);
3813 const rhs_id = try self.resolve(vec_cmp.rhs);4401 const rhs = try self.temporary(vec_cmp.rhs);
3814 const op = vec_cmp.compareOperator();4402 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);
3819 }4406 }
38204407
3821 /// Bitcast one type to another. Note: both types, input, output are expected in **direct** representation.4408 /// Bitcast one type to another. Note: both types, input, output are expected in **direct** representation.
...@@ -3881,7 +4468,8 @@ const DeclGen = struct {...@@ -3881,7 +4468,8 @@ const DeclGen = struct {
3881 // should we change the representation of strange integers?4468 // should we change the representation of strange integers?
3882 if (dst_ty.zigTypeTag(mod) == .Int) {4469 if (dst_ty.zigTypeTag(mod) == .Int) {
3883 const info = self.arithmeticTypeInfo(dst_ty);4470 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);
3885 }4473 }
38864474
3887 return result_id;4475 return result_id;
...@@ -3897,46 +4485,28 @@ const DeclGen = struct {...@@ -3897,46 +4485,28 @@ const DeclGen = struct {
38974485
3898 fn airIntCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4486 fn airIntCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3899 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;4487 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3900 const operand_id = try self.resolve(ty_op.operand);4488 const src = try self.temporary(ty_op.operand);
3901 const src_ty = self.typeOf(ty_op.operand);
3902 const dst_ty = self.typeOfIndex(inst);4489 const dst_ty = self.typeOfIndex(inst);
39034490
3904 const src_info = self.arithmeticTypeInfo(src_ty);4491 const src_info = self.arithmeticTypeInfo(src.ty);
3905 const dst_info = self.arithmeticTypeInfo(dst_ty);4492 const dst_info = self.arithmeticTypeInfo(dst_ty);
39064493
3907 if (src_info.backing_bits == dst_info.backing_bits) {4494 if (src_info.backing_bits == dst_info.backing_bits) {
3908 return operand_id;4495 return try src.materialize(self);
3909 }4496 }
39104497
3911 var wip = try self.elementWise(dst_ty, false);4498 const converted = try self.buildIntConvert(dst_ty, src);
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 }
39284499
3929 // Make sure to normalize the result if shrinking.4500 // Make sure to normalize the result if shrinking.
3930 // Because strange ints are sign extended in their backing4501 // Because strange ints are sign extended in their backing
3931 // type, we don't need to normalize when growing the type. The4502 // type, we don't need to normalize when growing the type. The
3932 // representation is already the same.4503 // representation is already the same.
3933 if (dst_info.bits < src_info.bits) {4504 const result = if (dst_info.bits < src_info.bits)
3934 result_id.* = try self.normalize(wip.ty, value_id, dst_info);4505 try self.normalize(converted, dst_info)
3935 } else {4506 else
3936 result_id.* = value_id;4507 converted;
3937 }4508
3938 }4509 return try result.materialize(self);
3939 return try wip.finalize();
3940 }4510 }
39414511
3942 fn intFromPtr(self: *DeclGen, operand_id: IdRef) !IdRef {4512 fn intFromPtr(self: *DeclGen, operand_id: IdRef) !IdRef {
...@@ -4011,16 +4581,9 @@ const DeclGen = struct {...@@ -4011,16 +4581,9 @@ const DeclGen = struct {
40114581
4012 fn airIntFromBool(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4582 fn airIntFromBool(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
4013 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;4583 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4014 const operand_id = try self.resolve(un_op);4584 const operand = try self.temporary(un_op);
4015 const result_ty = self.typeOfIndex(inst);4585 const result = try self.intFromBool(operand);
40164586 return try result.materialize(self);
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();
4024 }4587 }
40254588
4026 fn airFloatCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4589 fn airFloatCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
...@@ -4040,33 +4603,21 @@ const DeclGen = struct {...@@ -4040,33 +4603,21 @@ const DeclGen = struct {
40404603
4041 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4604 fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
4042 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;4605 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);
4044 const result_ty = self.typeOfIndex(inst);4607 const result_ty = self.typeOfIndex(inst);
4045 const info = self.arithmeticTypeInfo(result_ty);4608 const info = self.arithmeticTypeInfo(result_ty);
40464609
4047 var wip = try self.elementWise(result_ty, false);4610 const result = switch (info.class) {
4048 defer wip.deinit();4611 .bool => try self.buildUnary(.l_not, operand),
40494612 .float => unreachable,
4050 for (0..wip.results.len) |i| {4613 .composite_integer => unreachable, // TODO
4051 const args = .{4614 .strange_integer, .integer => blk: {
4052 .id_result_type = wip.ty_id,4615 const complement = try self.buildUnary(.bit_not, operand);
4053 .id_result = wip.allocId(i),4616 break :blk try self.normalize(complement, info);
4054 .operand = try wip.elementAt(result_ty, operand_id, i),4617 },
4055 };4618 };
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 }
40684619
4069 return try wip.finalize();4620 return try result.materialize(self);
4070 }4621 }
40714622
4072 fn airArrayToSlice(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {4623 fn airArrayToSlice(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
...@@ -4338,8 +4889,11 @@ const DeclGen = struct {...@@ -4338,8 +4889,11 @@ const DeclGen = struct {
4338 // For now, just generate a temporary and use that.4889 // For now, just generate a temporary and use that.
4339 // TODO: This backend probably also should use isByRef from llvm...4890 // 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;
4341 const ptr_array_ty_id = try self.ptrType2(array_ty, .Function, .direct);4895 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
4344 const tmp_id = self.spv.allocId();4898 const tmp_id = self.spv.allocId();
4345 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{4899 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
...@@ -4357,12 +4911,12 @@ const DeclGen = struct {...@@ -4357,12 +4911,12 @@ const DeclGen = struct {
43574911
4358 const result_id = self.spv.allocId();4912 const result_id = self.spv.allocId();
4359 try self.func.body.emit(self.spv.gpa, .OpLoad, .{4913 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),
4361 .id_result = result_id,4915 .id_result = result_id,
4362 .pointer = elem_ptr_id,4916 .pointer = elem_ptr_id,
4363 });4917 });
43644918
4365 if (array_ty.isVector(mod)) {4919 if (is_vector) {
4366 // Result is already in direct representation4920 // Result is already in direct representation
4367 return result_id;4921 return result_id;
4368 }4922 }
...@@ -4585,7 +5139,10 @@ const DeclGen = struct {...@@ -4585,7 +5139,10 @@ const DeclGen = struct {
4585 if (field_offset == 0) break :base_ptr_int field_ptr_int;5139 if (field_offset == 0) break :base_ptr_int field_ptr_int;
45865140
4587 const field_offset_id = try self.constInt(Type.usize, field_offset, .direct);5141 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);
4589 };5146 };
45905147
4591 const base_ptr = self.spv.allocId();5148 const base_ptr = self.spv.allocId();
...@@ -5400,13 +5957,17 @@ const DeclGen = struct {...@@ -5400,13 +5957,17 @@ const DeclGen = struct {
5400 else5957 else
5401 loaded_id;5958 loaded_id;
54025959
5403 const payload_ty_id = try self.resolveType(ptr_ty, .direct);5960 const ptr_ty_id = try self.resolveType(ptr_ty, .direct);
5404 const null_id = try self.spv.constNull(payload_ty_id);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
5405 const op: std.math.CompareOperator = switch (pred) {5965 const op: std.math.CompareOperator = switch (pred) {
5406 .is_null => .eq,5966 .is_null => .eq,
5407 .is_non_null => .neq,5967 .is_non_null => .neq,
5408 };5968 };
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);
5410 }5971 }
54115972
5412 const is_non_null_id = blk: {5973 const is_non_null_id = blk: {
src/codegen/spirv/Module.zig+15-7
...@@ -155,6 +155,9 @@ cache: struct {...@@ -155,6 +155,9 @@ cache: struct {
155 void_type: ?IdRef = null,155 void_type: ?IdRef = null,
156 int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, IdRef) = .{},156 int_types: std.AutoHashMapUnmanaged(std.builtin.Type.Int, IdRef) = .{},
157 float_types: std.AutoHashMapUnmanaged(std.builtin.Type.Float, IdRef) = .{},157 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) = .{},
158} = .{},161} = .{},
159162
160/// Set of Decls, referred to by Decl.Index.163/// Set of Decls, referred to by Decl.Index.
...@@ -194,6 +197,7 @@ pub fn deinit(self: *Module) void {...@@ -194,6 +197,7 @@ pub fn deinit(self: *Module) void {
194197
195 self.cache.int_types.deinit(self.gpa);198 self.cache.int_types.deinit(self.gpa);
196 self.cache.float_types.deinit(self.gpa);199 self.cache.float_types.deinit(self.gpa);
200 self.cache.vector_types.deinit(self.gpa);
197201
198 self.decls.deinit(self.gpa);202 self.decls.deinit(self.gpa);
199 self.decl_deps.deinit(self.gpa);203 self.decl_deps.deinit(self.gpa);
...@@ -474,13 +478,17 @@ pub fn floatType(self: *Module, bits: u16) !IdRef {...@@ -474,13 +478,17 @@ pub fn floatType(self: *Module, bits: u16) !IdRef {
474}478}
475479
476pub fn vectorType(self: *Module, len: u32, child_id: IdRef) !IdRef {480pub fn vectorType(self: *Module, len: u32, child_id: IdRef) !IdRef {
477 const result_id = self.allocId();481 const entry = try self.cache.vector_types.getOrPut(self.gpa, .{ child_id, len });
478 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVector, .{482 if (!entry.found_existing) {
479 .id_result = result_id,483 const result_id = self.allocId();
480 .component_type = child_id,484 entry.value_ptr.* = result_id;
481 .component_count = len,485 try self.sections.types_globals_constants.emit(self.gpa, .OpTypeVector, .{
482 });486 .id_result = result_id,
483 return result_id;487 .component_type = child_id,
488 .component_count = len,
489 });
490 }
491 return entry.value_ptr.*;
484}492}
485493
486pub fn constUndef(self: *Module, ty_id: IdRef) !IdRef {494pub fn constUndef(self: *Module, ty_id: IdRef) !IdRef {
test/behavior/abs.zig-1
...@@ -152,7 +152,6 @@ test "@abs int vectors" {...@@ -152,7 +152,6 @@ test "@abs int vectors" {
152 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO152 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
153 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO153 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
154 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;154 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
155 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
156155
157 try comptime testAbsIntVectors(1);156 try comptime testAbsIntVectors(1);
158 try testAbsIntVectors(1);157 try testAbsIntVectors(1);
test/behavior/floatop.zig-34
...@@ -275,7 +275,6 @@ test "@sqrt f16" {...@@ -275,7 +275,6 @@ test "@sqrt f16" {
275 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO275 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
276 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO276 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
277 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO277 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
278 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
279 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;278 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
280 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;279 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
281280
...@@ -287,7 +286,6 @@ test "@sqrt f32/f64" {...@@ -287,7 +286,6 @@ test "@sqrt f32/f64" {
287 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO286 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
288 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO287 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
289 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO288 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
290 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
291 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;289 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
292 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;290 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
293291
...@@ -389,7 +387,6 @@ test "@sqrt with vectors" {...@@ -389,7 +387,6 @@ test "@sqrt with vectors" {
389 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO387 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
390 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO388 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
391 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO389 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
392 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
393 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;390 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
394391
395 try testSqrtWithVectors();392 try testSqrtWithVectors();
...@@ -410,7 +407,6 @@ test "@sin f16" {...@@ -410,7 +407,6 @@ test "@sin f16" {
410 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO407 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
411 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO408 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
412 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO409 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
413 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
414 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;410 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
415 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;411 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
416412
...@@ -422,7 +418,6 @@ test "@sin f32/f64" {...@@ -422,7 +418,6 @@ test "@sin f32/f64" {
422 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO418 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
423 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO419 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
424 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO420 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
425 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
426 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;421 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
427 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;422 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
428423
...@@ -464,7 +459,6 @@ test "@sin with vectors" {...@@ -464,7 +459,6 @@ test "@sin with vectors" {
464 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO459 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
465 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO460 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
466 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO461 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
467 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
468 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;462 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
469 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;463 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
470464
...@@ -486,7 +480,6 @@ test "@cos f16" {...@@ -486,7 +480,6 @@ test "@cos f16" {
486 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO480 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
487 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO481 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
488 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO482 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
489 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
490 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;483 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
491 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;484 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
492485
...@@ -498,7 +491,6 @@ test "@cos f32/f64" {...@@ -498,7 +491,6 @@ test "@cos f32/f64" {
498 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO491 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
499 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO492 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
500 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO493 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
501 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
502 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;494 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
503 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;495 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
504496
...@@ -540,7 +532,6 @@ test "@cos with vectors" {...@@ -540,7 +532,6 @@ test "@cos with vectors" {
540 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO532 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
541 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO533 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
542 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO534 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
543 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
544 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;535 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
545 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;536 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
546537
...@@ -574,7 +565,6 @@ test "@tan f32/f64" {...@@ -574,7 +565,6 @@ test "@tan f32/f64" {
574 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO565 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
575 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO566 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
576 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO567 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
577 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
578 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;568 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
579 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;569 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
580570
...@@ -616,7 +606,6 @@ test "@tan with vectors" {...@@ -616,7 +606,6 @@ test "@tan with vectors" {
616 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO606 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
617 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO607 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
618 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO608 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
619 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
620 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;609 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
621 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;610 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
622611
...@@ -638,7 +627,6 @@ test "@exp f16" {...@@ -638,7 +627,6 @@ test "@exp f16" {
638 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO627 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
639 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO628 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
640 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO629 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
641 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
642 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;630 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
643 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;631 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
644632
...@@ -650,7 +638,6 @@ test "@exp f32/f64" {...@@ -650,7 +638,6 @@ test "@exp f32/f64" {
650 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO638 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
651 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO639 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
652 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO640 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
653 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
654 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;641 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
655 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;642 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
656643
...@@ -696,7 +683,6 @@ test "@exp with vectors" {...@@ -696,7 +683,6 @@ test "@exp with vectors" {
696 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO683 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
697 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO684 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
698 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO685 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
699 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
700 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;686 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
701 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;687 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
702688
...@@ -718,7 +704,6 @@ test "@exp2 f16" {...@@ -718,7 +704,6 @@ test "@exp2 f16" {
718 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO704 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
719 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO705 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
720 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO706 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
721 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
722 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;707 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
723 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;708 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
724709
...@@ -730,7 +715,6 @@ test "@exp2 f32/f64" {...@@ -730,7 +715,6 @@ test "@exp2 f32/f64" {
730 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO715 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
731 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO716 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
732 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO717 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
733 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
734 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;718 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
735 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;719 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
736720
...@@ -771,7 +755,6 @@ test "@exp2 with @vectors" {...@@ -771,7 +755,6 @@ test "@exp2 with @vectors" {
771 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO755 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
772 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO756 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
773 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO757 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
774 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
775 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;758 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
776 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;759 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
777760
...@@ -793,7 +776,6 @@ test "@log f16" {...@@ -793,7 +776,6 @@ test "@log f16" {
793 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO776 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
794 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO777 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
795 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO778 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
796 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
797 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;779 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
798 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;780 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
799781
...@@ -805,7 +787,6 @@ test "@log f32/f64" {...@@ -805,7 +787,6 @@ test "@log f32/f64" {
805 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO787 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
806 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO788 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
807 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO789 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
808 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
809 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;790 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
810 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;791 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
811792
...@@ -847,7 +828,6 @@ test "@log with @vectors" {...@@ -847,7 +828,6 @@ test "@log with @vectors" {
847 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO828 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
848 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO829 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
849 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO830 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
850 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
851 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;831 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
852 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;832 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
853833
...@@ -866,7 +846,6 @@ test "@log2 f16" {...@@ -866,7 +846,6 @@ test "@log2 f16" {
866 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO846 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
867 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO847 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
868 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO848 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
869 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
870 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;849 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
871 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;850 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
872851
...@@ -878,7 +857,6 @@ test "@log2 f32/f64" {...@@ -878,7 +857,6 @@ test "@log2 f32/f64" {
878 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO857 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
879 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO858 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
880 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO859 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
881 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
882 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;860 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
883 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;861 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
884862
...@@ -919,7 +897,6 @@ test "@log2 with vectors" {...@@ -919,7 +897,6 @@ test "@log2 with vectors" {
919 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO897 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
920 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO898 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
921 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO899 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
922 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
923 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;900 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
924 // https://github.com/ziglang/zig/issues/13681901 // https://github.com/ziglang/zig/issues/13681
925 if (builtin.zig_backend == .stage2_llvm and902 if (builtin.zig_backend == .stage2_llvm and
...@@ -945,7 +922,6 @@ test "@log10 f16" {...@@ -945,7 +922,6 @@ test "@log10 f16" {
945 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO922 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
946 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO923 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
947 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO924 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
948 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
949 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;925 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
950 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;926 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
951927
...@@ -957,7 +933,6 @@ test "@log10 f32/f64" {...@@ -957,7 +933,6 @@ test "@log10 f32/f64" {
957 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO933 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
958 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO934 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
959 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO935 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
960 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
961 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;936 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
962 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;937 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
963938
...@@ -998,7 +973,6 @@ test "@log10 with vectors" {...@@ -998,7 +973,6 @@ test "@log10 with vectors" {
998 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO973 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
999 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO974 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1000 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO975 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1001 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1002 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;976 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
1003 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;977 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1004978
...@@ -1243,7 +1217,6 @@ test "@ceil f16" {...@@ -1243,7 +1217,6 @@ test "@ceil f16" {
1243 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1217 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1244 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1218 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1245 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;1219 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;
1247 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1220 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
12481221
1249 try testCeil(f16);1222 try testCeil(f16);
...@@ -1255,7 +1228,6 @@ test "@ceil f32/f64" {...@@ -1255,7 +1228,6 @@ test "@ceil f32/f64" {
1255 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1228 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1256 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1229 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1257 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;1230 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;
1259 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1231 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
12601232
1261 try testCeil(f32);1233 try testCeil(f32);
...@@ -1320,7 +1292,6 @@ test "@ceil with vectors" {...@@ -1320,7 +1292,6 @@ test "@ceil with vectors" {
1320 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1292 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1321 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1293 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1322 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO1294 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
1323 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1324 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1295 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1325 if (builtin.zig_backend == .stage2_x86_64 and1296 if (builtin.zig_backend == .stage2_x86_64 and
1326 !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest;1297 !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest;
...@@ -1344,7 +1315,6 @@ test "@trunc f16" {...@@ -1344,7 +1315,6 @@ test "@trunc f16" {
1344 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1315 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1345 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1316 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1346 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;1317 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;
1348 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1318 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
13491319
1350 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isMIPS()) {1320 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isMIPS()) {
...@@ -1361,7 +1331,6 @@ test "@trunc f32/f64" {...@@ -1361,7 +1331,6 @@ test "@trunc f32/f64" {
1361 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1331 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1362 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1332 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1363 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;1333 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;
1365 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1334 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
13661335
1367 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isMIPS()) {1336 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch.isMIPS()) {
...@@ -1430,7 +1399,6 @@ fn testTrunc(comptime T: type) !void {...@@ -1430,7 +1399,6 @@ fn testTrunc(comptime T: type) !void {
1430test "@trunc with vectors" {1399test "@trunc with vectors" {
1431 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1400 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1432 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1401 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1433 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1434 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO1402 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
1435 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1403 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1436 if (builtin.zig_backend == .stage2_x86_64 and1404 if (builtin.zig_backend == .stage2_x86_64 and
...@@ -1454,7 +1422,6 @@ test "neg f16" {...@@ -1454,7 +1422,6 @@ test "neg f16" {
1454 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1422 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1455 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1423 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1456 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1424 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1457 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1458 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;1425 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
1459 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;1426 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1460 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1427 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
...@@ -1472,7 +1439,6 @@ test "neg f32/f64" {...@@ -1472,7 +1439,6 @@ test "neg f32/f64" {
1472 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1439 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1473 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1440 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1474 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1441 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1475 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1476 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;1442 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
1477 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1443 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
14781444
test/behavior/math.zig+54-5
...@@ -440,7 +440,6 @@ test "division" {...@@ -440,7 +440,6 @@ test "division" {
440 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO440 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
441 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO441 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
442 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO442 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
443 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
444 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;443 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
445 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;444 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
446 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;445 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
...@@ -530,7 +529,6 @@ test "division half-precision floats" {...@@ -530,7 +529,6 @@ test "division half-precision floats" {
530 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO529 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
531 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO530 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
532 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO531 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
533 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
534 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;532 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
535 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;533 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
536534
...@@ -622,7 +620,6 @@ test "negation wrapping" {...@@ -622,7 +620,6 @@ test "negation wrapping" {
622 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO620 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
623 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO621 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
624 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;622 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
625 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
626623
627 try expectEqual(@as(u1, 1), negateWrap(u1, 1));624 try expectEqual(@as(u1, 1), negateWrap(u1, 1));
628}625}
...@@ -1031,6 +1028,60 @@ test "@mulWithOverflow bitsize > 32" {...@@ -1031,6 +1028,60 @@ test "@mulWithOverflow bitsize > 32" {
1031 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO1028 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
1032 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1029 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
1034 {1085 {
1035 var a: u62 = 3;1086 var a: u62 = 3;
1036 _ = &a;1087 _ = &a;
...@@ -1580,7 +1631,6 @@ test "@round f16" {...@@ -1580,7 +1631,6 @@ test "@round f16" {
1580 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1631 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1581 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1632 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1582 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1633 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1583 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1584 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;1634 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1585 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1635 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
15861636
...@@ -1592,7 +1642,6 @@ test "@round f32/f64" {...@@ -1592,7 +1642,6 @@ test "@round f32/f64" {
1592 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1642 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1593 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1643 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1594 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1644 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1595 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1596 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;1645 if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
1597 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;1646 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
15981647
test/behavior/select.zig-2
...@@ -8,7 +8,6 @@ test "@select vectors" {...@@ -8,7 +8,6 @@ test "@select vectors" {
8 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO8 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
9 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO9 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
10 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO10 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
11 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
12 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;11 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
1312
14 try comptime selectVectors();13 try comptime selectVectors();
...@@ -39,7 +38,6 @@ test "@select arrays" {...@@ -39,7 +38,6 @@ test "@select arrays" {
39 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO38 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
40 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO39 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
41 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO40 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
42 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
43 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;41 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
44 if (builtin.zig_backend == .stage2_x86_64 and42 if (builtin.zig_backend == .stage2_x86_64 and
45 !comptime std.Target.x86.featureSetHas(builtin.cpu.features, .avx2)) return error.SkipZigTest;43 !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" {...@@ -548,7 +548,6 @@ test "vector division operators" {
548 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO548 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
549 if (builtin.zig_backend == .stage2_llvm and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest;549 if (builtin.zig_backend == .stage2_llvm and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest;
550 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO550 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
551 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
552 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;551 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
553552
554 const S = struct {553 const S = struct {