| ... | ... | @@ -373,8 +373,9 @@ const DeclGen = struct { |
| 373 | 373 | /// For `composite_integer` this is 0 (TODO) |
| 374 | 374 | backing_bits: u16, |
| 375 | 375 | |
| 376 | | /// Whether the type is a vector. |
| 377 | | is_vector: bool, |
| 376 | /// Null if this type is a scalar, or the length |
| 377 | /// of the vector otherwise. |
| 378 | vector_len: ?u32, |
| 378 | 379 | |
| 379 | 380 | /// Whether the inner type is signed. Only relevant for integers. |
| 380 | 381 | signedness: std.builtin.Signedness, |
| ... | ... | @@ -597,32 +598,37 @@ const DeclGen = struct { |
| 597 | 598 | return self.backingIntBits(ty) == null; |
| 598 | 599 | } |
| 599 | 600 | |
| 600 | | fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo { |
| 601 | fn arithmeticTypeInfo(self: *DeclGen, ty: Type) ArithmeticTypeInfo { |
| 601 | 602 | const mod = self.module; |
| 602 | 603 | const target = self.getTarget(); |
| 603 | | return switch (ty.zigTypeTag(mod)) { |
| 604 | var scalar_ty = ty.scalarType(mod); |
| 605 | if (scalar_ty.zigTypeTag(mod) == .Enum) { |
| 606 | scalar_ty = scalar_ty.intTagType(mod); |
| 607 | } |
| 608 | const vector_len = if (ty.isVector(mod)) ty.vectorLen(mod) else null; |
| 609 | return switch (scalar_ty.zigTypeTag(mod)) { |
| 604 | 610 | .Bool => ArithmeticTypeInfo{ |
| 605 | 611 | .bits = 1, // Doesn't matter for this class. |
| 606 | 612 | .backing_bits = self.backingIntBits(1).?, |
| 607 | | .is_vector = false, |
| 613 | .vector_len = vector_len, |
| 608 | 614 | .signedness = .unsigned, // Technically, but doesn't matter for this class. |
| 609 | 615 | .class = .bool, |
| 610 | 616 | }, |
| 611 | 617 | .Float => ArithmeticTypeInfo{ |
| 612 | | .bits = ty.floatBits(target), |
| 613 | | .backing_bits = ty.floatBits(target), // TODO: F80? |
| 614 | | .is_vector = false, |
| 618 | .bits = scalar_ty.floatBits(target), |
| 619 | .backing_bits = scalar_ty.floatBits(target), // TODO: F80? |
| 620 | .vector_len = vector_len, |
| 615 | 621 | .signedness = .signed, // Technically, but doesn't matter for this class. |
| 616 | 622 | .class = .float, |
| 617 | 623 | }, |
| 618 | 624 | .Int => blk: { |
| 619 | | const int_info = ty.intInfo(mod); |
| 625 | const int_info = scalar_ty.intInfo(mod); |
| 620 | 626 | // TODO: Maybe it's useful to also return this value. |
| 621 | 627 | const maybe_backing_bits = self.backingIntBits(int_info.bits); |
| 622 | 628 | break :blk ArithmeticTypeInfo{ |
| 623 | 629 | .bits = int_info.bits, |
| 624 | 630 | .backing_bits = maybe_backing_bits orelse 0, |
| 625 | | .is_vector = false, |
| 631 | .vector_len = vector_len, |
| 626 | 632 | .signedness = int_info.signedness, |
| 627 | 633 | .class = if (maybe_backing_bits) |backing_bits| |
| 628 | 634 | if (backing_bits == int_info.bits) |
| ... | ... | @@ -633,22 +639,9 @@ const DeclGen = struct { |
| 633 | 639 | .composite_integer, |
| 634 | 640 | }; |
| 635 | 641 | }, |
| 636 | | .Enum => return self.arithmeticTypeInfo(ty.intTagType(mod)), |
| 637 | | // As of yet, there is no vector support in the self-hosted compiler. |
| 638 | | .Vector => blk: { |
| 639 | | const child_type = ty.childType(mod); |
| 640 | | const child_ty_info = try self.arithmeticTypeInfo(child_type); |
| 641 | | break :blk ArithmeticTypeInfo{ |
| 642 | | .bits = child_ty_info.bits, |
| 643 | | .backing_bits = child_ty_info.backing_bits, |
| 644 | | .is_vector = true, |
| 645 | | .signedness = child_ty_info.signedness, |
| 646 | | .class = child_ty_info.class, |
| 647 | | }; |
| 648 | | }, |
| 649 | | // TODO: For which types is this the case? |
| 650 | | // else => self.todo("implement arithmeticTypeInfo for {}", .{ty.fmt(self.module)}), |
| 651 | | else => unreachable, |
| 642 | .Enum => unreachable, |
| 643 | .Vector => unreachable, |
| 644 | else => unreachable, // Unhandled arithmetic type |
| 652 | 645 | }; |
| 653 | 646 | } |
| 654 | 647 | |
| ... | ... | @@ -685,6 +678,18 @@ const DeclGen = struct { |
| 685 | 678 | } |
| 686 | 679 | } |
| 687 | 680 | |
| 681 | /// Emits a float constant |
| 682 | fn constFloat(self: *DeclGen, ty_ref: CacheRef, value: f128) !IdRef { |
| 683 | const ty = self.spv.cache.lookup(ty_ref).float_type; |
| 684 | return switch (ty.bits) { |
| 685 | 16 => try self.spv.resolveId(.{ .float = .{ .ty = ty_ref, .value = .{ .float16 = @floatCast(value) } } }), |
| 686 | 32 => try self.spv.resolveId(.{ .float = .{ .ty = ty_ref, .value = .{ .float32 = @floatCast(value) } } }), |
| 687 | 64 => try self.spv.resolveId(.{ .float = .{ .ty = ty_ref, .value = .{ .float64 = @floatCast(value) } } }), |
| 688 | 80, 128 => unreachable, // TODO |
| 689 | else => unreachable, |
| 690 | }; |
| 691 | } |
| 692 | |
| 688 | 693 | /// Construct a struct at runtime. |
| 689 | 694 | /// ty must be a struct type. |
| 690 | 695 | /// Constituents should be in `indirect` representation (as the elements of a struct should be). |
| ... | ... | @@ -1760,6 +1765,92 @@ const DeclGen = struct { |
| 1760 | 1765 | return union_layout; |
| 1761 | 1766 | } |
| 1762 | 1767 | |
| 1768 | /// This structure is used as helper for element-wise operations. It is intended |
| 1769 | /// to be used with both vectors and single elements. |
| 1770 | const WipElementWise = struct { |
| 1771 | dg: *DeclGen, |
| 1772 | result_ty: Type, |
| 1773 | /// Always in direct representation. |
| 1774 | result_ty_ref: CacheRef, |
| 1775 | scalar_ty: Type, |
| 1776 | /// Always in direct representation. |
| 1777 | scalar_ty_ref: CacheRef, |
| 1778 | scalar_ty_id: IdRef, |
| 1779 | /// True if the input is actually a vector type. |
| 1780 | is_vector: bool, |
| 1781 | /// The element-wise operation should fill these results before calling finalize(). |
| 1782 | /// These should all be in **direct** representation! `finalize()` will convert |
| 1783 | /// them to indirect if required. |
| 1784 | results: []IdRef, |
| 1785 | |
| 1786 | fn deinit(wip: *WipElementWise) void { |
| 1787 | wip.dg.gpa.free(wip.results); |
| 1788 | } |
| 1789 | |
| 1790 | /// Utility function to extract the element at a particular index in an |
| 1791 | /// input vector. This type is expected to be a vector if `wip.is_vector`, and |
| 1792 | /// a scalar otherwise. |
| 1793 | fn elementAt(wip: WipElementWise, ty: Type, value: IdRef, index: usize) !IdRef { |
| 1794 | const mod = wip.dg.module; |
| 1795 | if (wip.is_vector) { |
| 1796 | assert(ty.isVector(mod)); |
| 1797 | return try wip.dg.extractField(ty.childType(mod), value, @intCast(index)); |
| 1798 | } else { |
| 1799 | assert(!ty.isVector(mod)); |
| 1800 | assert(index == 0); |
| 1801 | return value; |
| 1802 | } |
| 1803 | } |
| 1804 | |
| 1805 | /// Turns the results of this WipElementWise into a result. This can either |
| 1806 | /// be a vector or single element, depending on `result_ty`. |
| 1807 | /// After calling this function, this WIP is no longer usable. |
| 1808 | /// Results is in `direct` representation. |
| 1809 | fn finalize(wip: *WipElementWise) !IdRef { |
| 1810 | if (wip.is_vector) { |
| 1811 | // Convert all the constituents to indirect, as required for the array. |
| 1812 | for (wip.results) |*result| { |
| 1813 | result.* = try wip.dg.convertToIndirect(wip.scalar_ty, result.*); |
| 1814 | } |
| 1815 | return try wip.dg.constructArray(wip.result_ty, wip.results); |
| 1816 | } else { |
| 1817 | return wip.results[0]; |
| 1818 | } |
| 1819 | } |
| 1820 | |
| 1821 | /// Allocate a result id at a particular index, and return it. |
| 1822 | fn allocId(wip: *WipElementWise, index: usize) IdRef { |
| 1823 | assert(wip.is_vector or index == 0); |
| 1824 | wip.results[index] = wip.dg.spv.allocId(); |
| 1825 | return wip.results[index]; |
| 1826 | } |
| 1827 | }; |
| 1828 | |
| 1829 | /// Create a new element-wise operation. |
| 1830 | fn elementWise(self: *DeclGen, result_ty: Type) !WipElementWise { |
| 1831 | const mod = self.module; |
| 1832 | // For now, this operation also reasons in terms of `.direct` representation. |
| 1833 | const result_ty_ref = try self.resolveType(result_ty, .direct); |
| 1834 | const is_vector = result_ty.isVector(mod); |
| 1835 | const num_results = if (is_vector) result_ty.vectorLen(mod) else 1; |
| 1836 | const results = try self.gpa.alloc(IdRef, num_results); |
| 1837 | for (results) |*result| result.* = undefined; |
| 1838 | |
| 1839 | const scalar_ty = result_ty.scalarType(mod); |
| 1840 | const scalar_ty_ref = try self.resolveType(scalar_ty, .direct); |
| 1841 | |
| 1842 | return .{ |
| 1843 | .dg = self, |
| 1844 | .result_ty = result_ty, |
| 1845 | .result_ty_ref = result_ty_ref, |
| 1846 | .scalar_ty = scalar_ty, |
| 1847 | .scalar_ty_ref = scalar_ty_ref, |
| 1848 | .scalar_ty_id = self.typeId(scalar_ty_ref), |
| 1849 | .is_vector = is_vector, |
| 1850 | .results = results, |
| 1851 | }; |
| 1852 | } |
| 1853 | |
| 1763 | 1854 | /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure. |
| 1764 | 1855 | /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry- |
| 1765 | 1856 | /// points. The test executor will then be able to invoke these to run the tests. |
| ... | ... | @@ -2081,25 +2172,31 @@ const DeclGen = struct { |
| 2081 | 2172 | const air_tags = self.air.instructions.items(.tag); |
| 2082 | 2173 | const maybe_result_id: ?IdRef = switch (air_tags[@intFromEnum(inst)]) { |
| 2083 | 2174 | // zig fmt: off |
| 2084 | | .add, .add_wrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true), |
| 2085 | | .sub, .sub_wrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true), |
| 2086 | | .mul, .mul_wrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true), |
| 2175 | .add, .add_wrap, .add_optimized => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd), |
| 2176 | .sub, .sub_wrap, .sub_optimized => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub), |
| 2177 | .mul, .mul_wrap, .mul_optimized => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul), |
| 2178 | |
| 2179 | .abs => try self.airAbs(inst), |
| 2087 | 2180 | |
| 2088 | 2181 | .div_float, |
| 2089 | 2182 | .div_float_optimized, |
| 2090 | 2183 | // TODO: Check that this is the right operation. |
| 2091 | 2184 | .div_trunc, |
| 2092 | 2185 | .div_trunc_optimized, |
| 2093 | | => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv, false), |
| 2186 | => try self.airArithOp(inst, .OpFDiv, .OpSDiv, .OpUDiv), |
| 2094 | 2187 | // TODO: Check if this is the right operation |
| 2095 | | // TODO: Make airArithOp for rem not emit a mask for the LHS. |
| 2096 | 2188 | .rem, |
| 2097 | 2189 | .rem_optimized, |
| 2098 | | => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem, false), |
| 2190 | => try self.airArithOp(inst, .OpFRem, .OpSRem, .OpSRem), |
| 2099 | 2191 | |
| 2100 | 2192 | .add_with_overflow => try self.airAddSubOverflow(inst, .OpIAdd, .OpULessThan, .OpSLessThan), |
| 2101 | 2193 | .sub_with_overflow => try self.airAddSubOverflow(inst, .OpISub, .OpUGreaterThan, .OpSGreaterThan), |
| 2194 | .shl_with_overflow => try self.airShlOverflow(inst), |
| 2102 | 2195 | |
| 2196 | .mul_add => try self.airMulAdd(inst), |
| 2197 | |
| 2198 | .splat => try self.airSplat(inst), |
| 2199 | .reduce, .reduce_optimized => try self.airReduce(inst), |
| 2103 | 2200 | .shuffle => try self.airShuffle(inst), |
| 2104 | 2201 | |
| 2105 | 2202 | .ptr_add => try self.airPtrAdd(inst), |
| ... | ... | @@ -2111,7 +2208,8 @@ const DeclGen = struct { |
| 2111 | 2208 | .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), |
| 2112 | 2209 | .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), |
| 2113 | 2210 | |
| 2114 | | .shl => try self.airShift(inst, .OpShiftLeftLogical), |
| 2211 | .shl, .shl_exact => try self.airShift(inst, .OpShiftLeftLogical, .OpShiftLeftLogical), |
| 2212 | .shr, .shr_exact => try self.airShift(inst, .OpShiftRightLogical, .OpShiftRightArithmetic), |
| 2115 | 2213 | |
| 2116 | 2214 | .min => try self.airMinMax(inst, .lt), |
| 2117 | 2215 | .max => try self.airMinMax(inst, .gt), |
| ... | ... | @@ -2121,6 +2219,7 @@ const DeclGen = struct { |
| 2121 | 2219 | .int_from_ptr => try self.airIntFromPtr(inst), |
| 2122 | 2220 | .float_from_int => try self.airFloatFromInt(inst), |
| 2123 | 2221 | .int_from_float => try self.airIntFromFloat(inst), |
| 2222 | .int_from_bool => try self.airIntFromBool(inst), |
| 2124 | 2223 | .fpext, .fptrunc => try self.airFloatCast(inst), |
| 2125 | 2224 | .not => try self.airNot(inst), |
| 2126 | 2225 | |
| ... | ... | @@ -2137,6 +2236,8 @@ const DeclGen = struct { |
| 2137 | 2236 | .ptr_elem_val => try self.airPtrElemVal(inst), |
| 2138 | 2237 | .array_elem_val => try self.airArrayElemVal(inst), |
| 2139 | 2238 | |
| 2239 | .vector_store_elem => return self.airVectorStoreElem(inst), |
| 2240 | |
| 2140 | 2241 | .set_union_tag => return self.airSetUnionTag(inst), |
| 2141 | 2242 | .get_union_tag => try self.airGetUnionTag(inst), |
| 2142 | 2243 | .union_init => try self.airUnionInit(inst), |
| ... | ... | @@ -2189,13 +2290,16 @@ const DeclGen = struct { |
| 2189 | 2290 | .wrap_errunion_err => try self.airWrapErrUnionErr(inst), |
| 2190 | 2291 | .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), |
| 2191 | 2292 | |
| 2192 | | .is_null => try self.airIsNull(inst, .is_null), |
| 2193 | | .is_non_null => try self.airIsNull(inst, .is_non_null), |
| 2194 | | .is_err => try self.airIsErr(inst, .is_err), |
| 2195 | | .is_non_err => try self.airIsErr(inst, .is_non_err), |
| 2293 | .is_null => try self.airIsNull(inst, false, .is_null), |
| 2294 | .is_non_null => try self.airIsNull(inst, false, .is_non_null), |
| 2295 | .is_null_ptr => try self.airIsNull(inst, true, .is_null), |
| 2296 | .is_non_null_ptr => try self.airIsNull(inst, true, .is_non_null), |
| 2297 | .is_err => try self.airIsErr(inst, .is_err), |
| 2298 | .is_non_err => try self.airIsErr(inst, .is_non_err), |
| 2196 | 2299 | |
| 2197 | | .optional_payload => try self.airUnwrapOptional(inst), |
| 2198 | | .wrap_optional => try self.airWrapOptional(inst), |
| 2300 | .optional_payload => try self.airUnwrapOptional(inst), |
| 2301 | .optional_payload_ptr => try self.airUnwrapOptionalPtr(inst), |
| 2302 | .wrap_optional => try self.airWrapOptional(inst), |
| 2199 | 2303 | |
| 2200 | 2304 | .assembly => try self.airAssembly(inst), |
| 2201 | 2305 | |
| ... | ... | @@ -2213,34 +2317,17 @@ const DeclGen = struct { |
| 2213 | 2317 | } |
| 2214 | 2318 | |
| 2215 | 2319 | fn binOpSimple(self: *DeclGen, ty: Type, lhs_id: IdRef, rhs_id: IdRef, comptime opcode: Opcode) !IdRef { |
| 2216 | | const mod = self.module; |
| 2217 | | |
| 2218 | | if (ty.isVector(mod)) { |
| 2219 | | const child_ty = ty.childType(mod); |
| 2220 | | const vector_len = ty.vectorLen(mod); |
| 2221 | | |
| 2222 | | const constituents = try self.gpa.alloc(IdRef, vector_len); |
| 2223 | | defer self.gpa.free(constituents); |
| 2224 | | |
| 2225 | | for (constituents, 0..) |*constituent, i| { |
| 2226 | | const lhs_index_id = try self.extractField(child_ty, lhs_id, @intCast(i)); |
| 2227 | | const rhs_index_id = try self.extractField(child_ty, rhs_id, @intCast(i)); |
| 2228 | | const result_id = try self.binOpSimple(child_ty, lhs_index_id, rhs_index_id, opcode); |
| 2229 | | constituent.* = try self.convertToIndirect(child_ty, result_id); |
| 2230 | | } |
| 2231 | | |
| 2232 | | return try self.constructArray(ty, constituents); |
| 2320 | var wip = try self.elementWise(ty); |
| 2321 | defer wip.deinit(); |
| 2322 | for (0..wip.results.len) |i| { |
| 2323 | try self.func.body.emit(self.spv.gpa, opcode, .{ |
| 2324 | .id_result_type = wip.scalar_ty_id, |
| 2325 | .id_result = wip.allocId(i), |
| 2326 | .operand_1 = try wip.elementAt(ty, lhs_id, i), |
| 2327 | .operand_2 = try wip.elementAt(ty, rhs_id, i), |
| 2328 | }); |
| 2233 | 2329 | } |
| 2234 | | |
| 2235 | | const result_id = self.spv.allocId(); |
| 2236 | | const result_type_id = try self.resolveTypeId(ty); |
| 2237 | | try self.func.body.emit(self.spv.gpa, opcode, .{ |
| 2238 | | .id_result_type = result_type_id, |
| 2239 | | .id_result = result_id, |
| 2240 | | .operand_1 = lhs_id, |
| 2241 | | .operand_2 = rhs_id, |
| 2242 | | }); |
| 2243 | | return result_id; |
| 2330 | return try wip.finalize(); |
| 2244 | 2331 | } |
| 2245 | 2332 | |
| 2246 | 2333 | fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { |
| ... | ... | @@ -2254,29 +2341,59 @@ const DeclGen = struct { |
| 2254 | 2341 | return try self.binOpSimple(ty, lhs_id, rhs_id, opcode); |
| 2255 | 2342 | } |
| 2256 | 2343 | |
| 2257 | | fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef { |
| 2344 | fn airShift(self: *DeclGen, inst: Air.Inst.Index, comptime unsigned: Opcode, comptime signed: Opcode) !?IdRef { |
| 2258 | 2345 | if (self.liveness.isUnused(inst)) return null; |
| 2346 | const mod = self.module; |
| 2259 | 2347 | const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 2260 | 2348 | const lhs_id = try self.resolve(bin_op.lhs); |
| 2261 | 2349 | const rhs_id = try self.resolve(bin_op.rhs); |
| 2262 | | const result_type_id = try self.resolveTypeId(self.typeOfIndex(inst)); |
| 2263 | 2350 | |
| 2264 | | // the shift and the base must be the same type in SPIR-V, but in Zig the shift is a smaller int. |
| 2265 | | const shift_id = self.spv.allocId(); |
| 2266 | | try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ |
| 2267 | | .id_result_type = result_type_id, |
| 2268 | | .id_result = shift_id, |
| 2269 | | .unsigned_value = rhs_id, |
| 2270 | | }); |
| 2351 | const result_ty = self.typeOfIndex(inst); |
| 2352 | const shift_ty = self.typeOf(bin_op.rhs); |
| 2353 | const scalar_shift_ty_ref = try self.resolveType(shift_ty.scalarType(mod), .direct); |
| 2271 | 2354 | |
| 2272 | | const result_id = self.spv.allocId(); |
| 2273 | | try self.func.body.emit(self.spv.gpa, opcode, .{ |
| 2274 | | .id_result_type = result_type_id, |
| 2275 | | .id_result = result_id, |
| 2276 | | .base = lhs_id, |
| 2277 | | .shift = shift_id, |
| 2278 | | }); |
| 2279 | | return result_id; |
| 2355 | const info = self.arithmeticTypeInfo(result_ty); |
| 2356 | switch (info.class) { |
| 2357 | .composite_integer => return self.todo("shift ops for composite integers", .{}), |
| 2358 | .integer, .strange_integer => {}, |
| 2359 | .float, .bool => unreachable, |
| 2360 | } |
| 2361 | |
| 2362 | var wip = try self.elementWise(result_ty); |
| 2363 | defer wip.deinit(); |
| 2364 | for (wip.results, 0..) |*result_id, i| { |
| 2365 | const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); |
| 2366 | const rhs_elem_id = try wip.elementAt(shift_ty, rhs_id, i); |
| 2367 | |
| 2368 | // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, |
| 2369 | // so just manually upcast it if required. |
| 2370 | const shift_id = if (scalar_shift_ty_ref != wip.scalar_ty_ref) blk: { |
| 2371 | const shift_id = self.spv.allocId(); |
| 2372 | try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ |
| 2373 | .id_result_type = wip.scalar_ty_id, |
| 2374 | .id_result = shift_id, |
| 2375 | .unsigned_value = rhs_elem_id, |
| 2376 | }); |
| 2377 | break :blk shift_id; |
| 2378 | } else rhs_elem_id; |
| 2379 | |
| 2380 | const value_id = self.spv.allocId(); |
| 2381 | const args = .{ |
| 2382 | .id_result_type = wip.scalar_ty_id, |
| 2383 | .id_result = value_id, |
| 2384 | .base = lhs_elem_id, |
| 2385 | .shift = shift_id, |
| 2386 | }; |
| 2387 | |
| 2388 | if (result_ty.isSignedInt(mod)) { |
| 2389 | try self.func.body.emit(self.spv.gpa, signed, args); |
| 2390 | } else { |
| 2391 | try self.func.body.emit(self.spv.gpa, unsigned, args); |
| 2392 | } |
| 2393 | |
| 2394 | result_id.* = try self.normalize(wip.scalar_ty_ref, value_id, info); |
| 2395 | } |
| 2396 | return try wip.finalize(); |
| 2280 | 2397 | } |
| 2281 | 2398 | |
| 2282 | 2399 | fn airMinMax(self: *DeclGen, inst: Air.Inst.Index, op: std.math.CompareOperator) !?IdRef { |
| ... | ... | @@ -2286,88 +2403,102 @@ const DeclGen = struct { |
| 2286 | 2403 | const lhs_id = try self.resolve(bin_op.lhs); |
| 2287 | 2404 | const rhs_id = try self.resolve(bin_op.rhs); |
| 2288 | 2405 | const result_ty = self.typeOfIndex(inst); |
| 2289 | | const result_ty_ref = try self.resolveType(result_ty, .direct); |
| 2290 | | |
| 2291 | | const info = try self.arithmeticTypeInfo(result_ty); |
| 2292 | | // TODO: Use fmin for OpenCL |
| 2293 | | const cmp_id = try self.cmp(op, Type.bool, result_ty, lhs_id, rhs_id); |
| 2294 | | const selection_id = switch (info.class) { |
| 2295 | | .float => blk: { |
| 2296 | | // cmp uses OpFOrd. When we have 0 [<>] nan this returns false, |
| 2297 | | // but we want it to pick lhs. Therefore we also have to check if |
| 2298 | | // rhs is nan. We don't need to care about the result when both |
| 2299 | | // are nan. |
| 2300 | | const rhs_is_nan_id = self.spv.allocId(); |
| 2301 | | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 2302 | | try self.func.body.emit(self.spv.gpa, .OpIsNan, .{ |
| 2303 | | .id_result_type = self.typeId(bool_ty_ref), |
| 2304 | | .id_result = rhs_is_nan_id, |
| 2305 | | .x = rhs_id, |
| 2306 | | }); |
| 2307 | | const float_cmp_id = self.spv.allocId(); |
| 2308 | | try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{ |
| 2309 | | .id_result_type = self.typeId(bool_ty_ref), |
| 2310 | | .id_result = float_cmp_id, |
| 2311 | | .operand_1 = cmp_id, |
| 2312 | | .operand_2 = rhs_is_nan_id, |
| 2313 | | }); |
| 2314 | | break :blk float_cmp_id; |
| 2315 | | }, |
| 2316 | | else => cmp_id, |
| 2317 | | }; |
| 2318 | 2406 | |
| 2319 | | const result_id = self.spv.allocId(); |
| 2320 | | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ |
| 2321 | | .id_result_type = self.typeId(result_ty_ref), |
| 2322 | | .id_result = result_id, |
| 2323 | | .condition = selection_id, |
| 2324 | | .object_1 = lhs_id, |
| 2325 | | .object_2 = rhs_id, |
| 2326 | | }); |
| 2327 | | return result_id; |
| 2328 | | } |
| 2407 | return try self.minMax(result_ty, op, lhs_id, rhs_id); |
| 2408 | } |
| 2409 | |
| 2410 | fn minMax(self: *DeclGen, result_ty: Type, op: std.math.CompareOperator, lhs_id: IdRef, rhs_id: IdRef) !IdRef { |
| 2411 | const info = self.arithmeticTypeInfo(result_ty); |
| 2412 | |
| 2413 | var wip = try self.elementWise(result_ty); |
| 2414 | defer wip.deinit(); |
| 2415 | for (wip.results, 0..) |*result_id, i| { |
| 2416 | const lhs_elem_id = try wip.elementAt(result_ty, lhs_id, i); |
| 2417 | const rhs_elem_id = try wip.elementAt(result_ty, rhs_id, i); |
| 2418 | |
| 2419 | // TODO: Use fmin for OpenCL |
| 2420 | const cmp_id = try self.cmp(op, Type.bool, wip.scalar_ty, lhs_elem_id, rhs_elem_id); |
| 2421 | const selection_id = switch (info.class) { |
| 2422 | .float => blk: { |
| 2423 | // cmp uses OpFOrd. When we have 0 [<>] nan this returns false, |
| 2424 | // but we want it to pick lhs. Therefore we also have to check if |
| 2425 | // rhs is nan. We don't need to care about the result when both |
| 2426 | // are nan. |
| 2427 | const rhs_is_nan_id = self.spv.allocId(); |
| 2428 | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 2429 | try self.func.body.emit(self.spv.gpa, .OpIsNan, .{ |
| 2430 | .id_result_type = self.typeId(bool_ty_ref), |
| 2431 | .id_result = rhs_is_nan_id, |
| 2432 | .x = rhs_elem_id, |
| 2433 | }); |
| 2434 | const float_cmp_id = self.spv.allocId(); |
| 2435 | try self.func.body.emit(self.spv.gpa, .OpLogicalOr, .{ |
| 2436 | .id_result_type = self.typeId(bool_ty_ref), |
| 2437 | .id_result = float_cmp_id, |
| 2438 | .operand_1 = cmp_id, |
| 2439 | .operand_2 = rhs_is_nan_id, |
| 2440 | }); |
| 2441 | break :blk float_cmp_id; |
| 2442 | }, |
| 2443 | else => cmp_id, |
| 2444 | }; |
| 2329 | 2445 | |
| 2330 | | /// This function canonicalizes a "strange" integer value: |
| 2331 | | /// For unsigned integers, the value is masked so that only the relevant bits can contain |
| 2332 | | /// non-zeros. |
| 2333 | | /// For signed integers, the value is also sign extended. |
| 2334 | | fn normalizeInt(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, info: ArithmeticTypeInfo) !IdRef { |
| 2335 | | assert(info.class != .composite_integer); // TODO |
| 2336 | | if (info.bits == info.backing_bits) { |
| 2337 | | return value_id; |
| 2446 | result_id.* = self.spv.allocId(); |
| 2447 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ |
| 2448 | .id_result_type = wip.scalar_ty_id, |
| 2449 | .id_result = result_id.*, |
| 2450 | .condition = selection_id, |
| 2451 | .object_1 = lhs_elem_id, |
| 2452 | .object_2 = rhs_elem_id, |
| 2453 | }); |
| 2338 | 2454 | } |
| 2339 | | |
| 2340 | | switch (info.signedness) { |
| 2341 | | .unsigned => { |
| 2342 | | const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; |
| 2343 | | const result_id = self.spv.allocId(); |
| 2344 | | const mask_id = try self.constInt(ty_ref, mask_value); |
| 2345 | | try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ |
| 2346 | | .id_result_type = self.typeId(ty_ref), |
| 2347 | | .id_result = result_id, |
| 2348 | | .operand_1 = value_id, |
| 2349 | | .operand_2 = mask_id, |
| 2350 | | }); |
| 2351 | | return result_id; |
| 2352 | | }, |
| 2353 | | .signed => { |
| 2354 | | // Shift left and right so that we can copy the sight bit that way. |
| 2355 | | const shift_amt_id = try self.constInt(ty_ref, info.backing_bits - info.bits); |
| 2356 | | const left_id = self.spv.allocId(); |
| 2357 | | try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ |
| 2358 | | .id_result_type = self.typeId(ty_ref), |
| 2359 | | .id_result = left_id, |
| 2360 | | .base = value_id, |
| 2361 | | .shift = shift_amt_id, |
| 2362 | | }); |
| 2363 | | const right_id = self.spv.allocId(); |
| 2364 | | try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ |
| 2365 | | .id_result_type = self.typeId(ty_ref), |
| 2366 | | .id_result = right_id, |
| 2367 | | .base = left_id, |
| 2368 | | .shift = shift_amt_id, |
| 2369 | | }); |
| 2370 | | return right_id; |
| 2455 | return wip.finalize(); |
| 2456 | } |
| 2457 | |
| 2458 | /// This function normalizes values to a canonical representation |
| 2459 | /// after some arithmetic operation. This mostly consists of wrapping |
| 2460 | /// behavior for strange integers: |
| 2461 | /// - Unsigned integers are bitwise masked with a mask that only passes |
| 2462 | /// the valid bits through. |
| 2463 | /// - Signed integers are also sign extended if they are negative. |
| 2464 | /// All other values are returned unmodified (this makes strange integer |
| 2465 | /// wrapping easier to use in generic operations). |
| 2466 | fn normalize(self: *DeclGen, ty_ref: CacheRef, value_id: IdRef, info: ArithmeticTypeInfo) !IdRef { |
| 2467 | switch (info.class) { |
| 2468 | .integer, .bool, .float => return value_id, |
| 2469 | .composite_integer => unreachable, // TODO |
| 2470 | .strange_integer => switch (info.signedness) { |
| 2471 | .unsigned => { |
| 2472 | const mask_value = if (info.bits == 64) 0xFFFF_FFFF_FFFF_FFFF else (@as(u64, 1) << @as(u6, @intCast(info.bits))) - 1; |
| 2473 | const result_id = self.spv.allocId(); |
| 2474 | const mask_id = try self.constInt(ty_ref, mask_value); |
| 2475 | try self.func.body.emit(self.spv.gpa, .OpBitwiseAnd, .{ |
| 2476 | .id_result_type = self.typeId(ty_ref), |
| 2477 | .id_result = result_id, |
| 2478 | .operand_1 = value_id, |
| 2479 | .operand_2 = mask_id, |
| 2480 | }); |
| 2481 | return result_id; |
| 2482 | }, |
| 2483 | .signed => { |
| 2484 | // Shift left and right so that we can copy the sight bit that way. |
| 2485 | const shift_amt_id = try self.constInt(ty_ref, info.backing_bits - info.bits); |
| 2486 | const left_id = self.spv.allocId(); |
| 2487 | try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ |
| 2488 | .id_result_type = self.typeId(ty_ref), |
| 2489 | .id_result = left_id, |
| 2490 | .base = value_id, |
| 2491 | .shift = shift_amt_id, |
| 2492 | }); |
| 2493 | const right_id = self.spv.allocId(); |
| 2494 | try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ |
| 2495 | .id_result_type = self.typeId(ty_ref), |
| 2496 | .id_result = right_id, |
| 2497 | .base = left_id, |
| 2498 | .shift = shift_amt_id, |
| 2499 | }); |
| 2500 | return right_id; |
| 2501 | }, |
| 2371 | 2502 | }, |
| 2372 | 2503 | } |
| 2373 | 2504 | } |
| ... | ... | @@ -2378,8 +2509,6 @@ const DeclGen = struct { |
| 2378 | 2509 | comptime fop: Opcode, |
| 2379 | 2510 | comptime sop: Opcode, |
| 2380 | 2511 | comptime uop: Opcode, |
| 2381 | | /// true if this operation holds under modular arithmetic. |
| 2382 | | comptime modular: bool, |
| 2383 | 2512 | ) !?IdRef { |
| 2384 | 2513 | if (self.liveness.isUnused(inst)) return null; |
| 2385 | 2514 | |
| ... | ... | @@ -2393,60 +2522,27 @@ const DeclGen = struct { |
| 2393 | 2522 | assert(self.typeOf(bin_op.lhs).eql(ty, self.module)); |
| 2394 | 2523 | assert(self.typeOf(bin_op.rhs).eql(ty, self.module)); |
| 2395 | 2524 | |
| 2396 | | return try self.arithOp(ty, lhs_id, rhs_id, fop, sop, uop, modular); |
| 2525 | return try self.arithOp(ty, lhs_id, rhs_id, fop, sop, uop); |
| 2397 | 2526 | } |
| 2398 | 2527 | |
| 2399 | 2528 | fn arithOp( |
| 2400 | 2529 | self: *DeclGen, |
| 2401 | 2530 | ty: Type, |
| 2402 | | lhs_id_: IdRef, |
| 2403 | | rhs_id_: IdRef, |
| 2531 | lhs_id: IdRef, |
| 2532 | rhs_id: IdRef, |
| 2404 | 2533 | comptime fop: Opcode, |
| 2405 | 2534 | comptime sop: Opcode, |
| 2406 | 2535 | comptime uop: Opcode, |
| 2407 | | /// true if this operation holds under modular arithmetic. |
| 2408 | | comptime modular: bool, |
| 2409 | 2536 | ) !IdRef { |
| 2410 | | var rhs_id = rhs_id_; |
| 2411 | | var lhs_id = lhs_id_; |
| 2412 | | |
| 2413 | | const mod = self.module; |
| 2414 | | const result_ty_ref = try self.resolveType(ty, .direct); |
| 2415 | | |
| 2416 | | if (ty.isVector(mod)) { |
| 2417 | | const child_ty = ty.childType(mod); |
| 2418 | | const vector_len = ty.vectorLen(mod); |
| 2419 | | const constituents = try self.gpa.alloc(IdRef, vector_len); |
| 2420 | | defer self.gpa.free(constituents); |
| 2421 | | |
| 2422 | | for (constituents, 0..) |*constituent, i| { |
| 2423 | | const lhs_index_id = try self.extractField(child_ty, lhs_id, @intCast(i)); |
| 2424 | | const rhs_index_id = try self.extractField(child_ty, rhs_id, @intCast(i)); |
| 2425 | | constituent.* = try self.arithOp(child_ty, lhs_index_id, rhs_index_id, fop, sop, uop, modular); |
| 2426 | | } |
| 2427 | | |
| 2428 | | return self.constructArray(ty, constituents); |
| 2429 | | } |
| 2430 | | |
| 2431 | 2537 | // Binary operations are generally applicable to both scalar and vector operations |
| 2432 | 2538 | // in SPIR-V, but int and float versions of operations require different opcodes. |
| 2433 | | const info = try self.arithmeticTypeInfo(ty); |
| 2539 | const info = self.arithmeticTypeInfo(ty); |
| 2434 | 2540 | |
| 2435 | 2541 | const opcode_index: usize = switch (info.class) { |
| 2436 | 2542 | .composite_integer => { |
| 2437 | 2543 | return self.todo("binary operations for composite integers", .{}); |
| 2438 | 2544 | }, |
| 2439 | | .strange_integer => blk: { |
| 2440 | | if (!modular) { |
| 2441 | | lhs_id = try self.normalizeInt(result_ty_ref, lhs_id, info); |
| 2442 | | rhs_id = try self.normalizeInt(result_ty_ref, rhs_id, info); |
| 2443 | | } |
| 2444 | | break :blk switch (info.signedness) { |
| 2445 | | .signed => @as(usize, 1), |
| 2446 | | .unsigned => @as(usize, 2), |
| 2447 | | }; |
| 2448 | | }, |
| 2449 | | .integer => switch (info.signedness) { |
| 2545 | .integer, .strange_integer => switch (info.signedness) { |
| 2450 | 2546 | .signed => @as(usize, 1), |
| 2451 | 2547 | .unsigned => @as(usize, 2), |
| 2452 | 2548 | }, |
| ... | ... | @@ -2454,24 +2550,91 @@ const DeclGen = struct { |
| 2454 | 2550 | .bool => unreachable, |
| 2455 | 2551 | }; |
| 2456 | 2552 | |
| 2457 | | const result_id = self.spv.allocId(); |
| 2458 | | const operands = .{ |
| 2459 | | .id_result_type = self.typeId(result_ty_ref), |
| 2460 | | .id_result = result_id, |
| 2461 | | .operand_1 = lhs_id, |
| 2462 | | .operand_2 = rhs_id, |
| 2463 | | }; |
| 2553 | var wip = try self.elementWise(ty); |
| 2554 | defer wip.deinit(); |
| 2555 | for (wip.results, 0..) |*result_id, i| { |
| 2556 | const lhs_elem_id = try wip.elementAt(ty, lhs_id, i); |
| 2557 | const rhs_elem_id = try wip.elementAt(ty, rhs_id, i); |
| 2558 | |
| 2559 | const value_id = self.spv.allocId(); |
| 2560 | const operands = .{ |
| 2561 | .id_result_type = wip.scalar_ty_id, |
| 2562 | .id_result = value_id, |
| 2563 | .operand_1 = lhs_elem_id, |
| 2564 | .operand_2 = rhs_elem_id, |
| 2565 | }; |
| 2464 | 2566 | |
| 2465 | | switch (opcode_index) { |
| 2466 | | 0 => try self.func.body.emit(self.spv.gpa, fop, operands), |
| 2467 | | 1 => try self.func.body.emit(self.spv.gpa, sop, operands), |
| 2468 | | 2 => try self.func.body.emit(self.spv.gpa, uop, operands), |
| 2469 | | else => unreachable, |
| 2567 | switch (opcode_index) { |
| 2568 | 0 => try self.func.body.emit(self.spv.gpa, fop, operands), |
| 2569 | 1 => try self.func.body.emit(self.spv.gpa, sop, operands), |
| 2570 | 2 => try self.func.body.emit(self.spv.gpa, uop, operands), |
| 2571 | else => unreachable, |
| 2572 | } |
| 2573 | |
| 2574 | // TODO: Trap on overflow? Probably going to be annoying. |
| 2575 | // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. |
| 2576 | result_id.* = try self.normalize(wip.scalar_ty_ref, value_id, info); |
| 2470 | 2577 | } |
| 2471 | | // TODO: Trap on overflow? Probably going to be annoying. |
| 2472 | | // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. |
| 2473 | 2578 | |
| 2474 | | return result_id; |
| 2579 | return try wip.finalize(); |
| 2580 | } |
| 2581 | |
| 2582 | fn airAbs(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2583 | if (self.liveness.isUnused(inst)) return null; |
| 2584 | |
| 2585 | const mod = self.module; |
| 2586 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 2587 | const operand_id = try self.resolve(ty_op.operand); |
| 2588 | // Note: operand_ty may be signed, while ty is always unsigned! |
| 2589 | const operand_ty = self.typeOf(ty_op.operand); |
| 2590 | const ty = self.typeOfIndex(inst); |
| 2591 | const info = self.arithmeticTypeInfo(ty); |
| 2592 | const operand_scalar_ty = operand_ty.scalarType(mod); |
| 2593 | const operand_scalar_ty_ref = try self.resolveType(operand_scalar_ty, .direct); |
| 2594 | |
| 2595 | var wip = try self.elementWise(ty); |
| 2596 | defer wip.deinit(); |
| 2597 | |
| 2598 | const zero_id = switch (info.class) { |
| 2599 | .float => try self.constFloat(operand_scalar_ty_ref, 0), |
| 2600 | .integer, .strange_integer => try self.constInt(operand_scalar_ty_ref, 0), |
| 2601 | .composite_integer => unreachable, // TODO |
| 2602 | .bool => unreachable, |
| 2603 | }; |
| 2604 | for (wip.results, 0..) |*result_id, i| { |
| 2605 | const elem_id = try wip.elementAt(operand_ty, operand_id, i); |
| 2606 | // Idk why spir-v doesn't have a dedicated abs() instruction in the base |
| 2607 | // instruction set. For now we're just going to negate and check to avoid |
| 2608 | // importing the extinst. |
| 2609 | // TODO: Make this a call to compiler rt / ext inst |
| 2610 | const neg_id = self.spv.allocId(); |
| 2611 | const args = .{ |
| 2612 | .id_result_type = self.typeId(operand_scalar_ty_ref), |
| 2613 | .id_result = neg_id, |
| 2614 | .operand_1 = zero_id, |
| 2615 | .operand_2 = elem_id, |
| 2616 | }; |
| 2617 | switch (info.class) { |
| 2618 | .float => try self.func.body.emit(self.spv.gpa, .OpFSub, args), |
| 2619 | .integer, .strange_integer => try self.func.body.emit(self.spv.gpa, .OpISub, args), |
| 2620 | .composite_integer => unreachable, // TODO |
| 2621 | .bool => unreachable, |
| 2622 | } |
| 2623 | const neg_norm_id = try self.normalize(wip.scalar_ty_ref, neg_id, info); |
| 2624 | |
| 2625 | const gt_zero_id = try self.cmp(.gt, Type.bool, operand_scalar_ty, elem_id, zero_id); |
| 2626 | const abs_id = self.spv.allocId(); |
| 2627 | try self.func.body.emit(self.spv.gpa, .OpSelect, .{ |
| 2628 | .id_result_type = self.typeId(operand_scalar_ty_ref), |
| 2629 | .id_result = abs_id, |
| 2630 | .condition = gt_zero_id, |
| 2631 | .object_1 = elem_id, |
| 2632 | .object_2 = neg_norm_id, |
| 2633 | }); |
| 2634 | // For Shader, we may need to cast from signed to unsigned here. |
| 2635 | result_id.* = try self.bitCast(wip.scalar_ty, operand_scalar_ty, abs_id); |
| 2636 | } |
| 2637 | return try wip.finalize(); |
| 2475 | 2638 | } |
| 2476 | 2639 | |
| 2477 | 2640 | fn airAddSubOverflow( |
| ... | ... | @@ -2488,140 +2651,344 @@ const DeclGen = struct { |
| 2488 | 2651 | const lhs = try self.resolve(extra.lhs); |
| 2489 | 2652 | const rhs = try self.resolve(extra.rhs); |
| 2490 | 2653 | |
| 2491 | | const operand_ty = self.typeOf(extra.lhs); |
| 2492 | 2654 | const result_ty = self.typeOfIndex(inst); |
| 2655 | const operand_ty = self.typeOf(extra.lhs); |
| 2656 | const ov_ty = result_ty.structFieldType(1, self.module); |
| 2657 | |
| 2658 | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 2493 | 2659 | |
| 2494 | | const info = try self.arithmeticTypeInfo(operand_ty); |
| 2660 | const info = self.arithmeticTypeInfo(operand_ty); |
| 2495 | 2661 | switch (info.class) { |
| 2496 | 2662 | .composite_integer => return self.todo("overflow ops for composite integers", .{}), |
| 2497 | | .strange_integer => return self.todo("overflow ops for strange integers", .{}), |
| 2498 | | .integer => {}, |
| 2663 | .strange_integer, .integer => {}, |
| 2499 | 2664 | .float, .bool => unreachable, |
| 2500 | 2665 | } |
| 2501 | 2666 | |
| 2502 | | // The operand type must be the same as the result type in SPIR-V, which |
| 2503 | | // is the same as in Zig. |
| 2504 | | const operand_ty_ref = try self.resolveType(operand_ty, .direct); |
| 2505 | | const operand_ty_id = self.typeId(operand_ty_ref); |
| 2667 | var wip_result = try self.elementWise(operand_ty); |
| 2668 | defer wip_result.deinit(); |
| 2669 | var wip_ov = try self.elementWise(ov_ty); |
| 2670 | defer wip_ov.deinit(); |
| 2671 | for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| { |
| 2672 | const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i); |
| 2673 | const rhs_elem_id = try wip_result.elementAt(operand_ty, rhs, i); |
| 2674 | |
| 2675 | // Normalize both so that we can properly check for overflow |
| 2676 | const value_id = self.spv.allocId(); |
| 2677 | |
| 2678 | try self.func.body.emit(self.spv.gpa, add, .{ |
| 2679 | .id_result_type = wip_result.scalar_ty_id, |
| 2680 | .id_result = value_id, |
| 2681 | .operand_1 = lhs_elem_id, |
| 2682 | .operand_2 = rhs_elem_id, |
| 2683 | }); |
| 2506 | 2684 | |
| 2507 | | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 2685 | // Normalize the result so that the comparisons go well |
| 2686 | result_id.* = try self.normalize(wip_result.scalar_ty_ref, value_id, info); |
| 2687 | |
| 2688 | const overflowed_id = switch (info.signedness) { |
| 2689 | .unsigned => blk: { |
| 2690 | // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. |
| 2691 | // For subtraction the conditions need to be swapped. |
| 2692 | const overflowed_id = self.spv.allocId(); |
| 2693 | try self.func.body.emit(self.spv.gpa, ucmp, .{ |
| 2694 | .id_result_type = self.typeId(bool_ty_ref), |
| 2695 | .id_result = overflowed_id, |
| 2696 | .operand_1 = result_id.*, |
| 2697 | .operand_2 = lhs_elem_id, |
| 2698 | }); |
| 2699 | break :blk overflowed_id; |
| 2700 | }, |
| 2701 | .signed => blk: { |
| 2702 | // lhs - rhs |
| 2703 | // For addition, overflow happened if: |
| 2704 | // - rhs is negative and value > lhs |
| 2705 | // - rhs is positive and value < lhs |
| 2706 | // This can be shortened to: |
| 2707 | // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs) |
| 2708 | // = (rhs < 0) == (value > lhs) |
| 2709 | // = (rhs < 0) == (lhs < value) |
| 2710 | // Note that signed overflow is also wrapping in spir-v. |
| 2711 | // For subtraction, overflow happened if: |
| 2712 | // - rhs is negative and value < lhs |
| 2713 | // - rhs is positive and value > lhs |
| 2714 | // This can be shortened to: |
| 2715 | // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs) |
| 2716 | // = (rhs < 0) == (value < lhs) |
| 2717 | // = (rhs < 0) == (lhs > value) |
| 2718 | |
| 2719 | const rhs_lt_zero_id = self.spv.allocId(); |
| 2720 | const zero_id = try self.constInt(wip_result.scalar_ty_ref, 0); |
| 2721 | try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ |
| 2722 | .id_result_type = self.typeId(bool_ty_ref), |
| 2723 | .id_result = rhs_lt_zero_id, |
| 2724 | .operand_1 = rhs_elem_id, |
| 2725 | .operand_2 = zero_id, |
| 2726 | }); |
| 2727 | |
| 2728 | const value_gt_lhs_id = self.spv.allocId(); |
| 2729 | try self.func.body.emit(self.spv.gpa, scmp, .{ |
| 2730 | .id_result_type = self.typeId(bool_ty_ref), |
| 2731 | .id_result = value_gt_lhs_id, |
| 2732 | .operand_1 = lhs_elem_id, |
| 2733 | .operand_2 = result_id.*, |
| 2734 | }); |
| 2735 | |
| 2736 | const overflowed_id = self.spv.allocId(); |
| 2737 | try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{ |
| 2738 | .id_result_type = self.typeId(bool_ty_ref), |
| 2739 | .id_result = overflowed_id, |
| 2740 | .operand_1 = rhs_lt_zero_id, |
| 2741 | .operand_2 = value_gt_lhs_id, |
| 2742 | }); |
| 2743 | break :blk overflowed_id; |
| 2744 | }, |
| 2745 | }; |
| 2746 | |
| 2747 | ov_id.* = try self.intFromBool(wip_ov.scalar_ty_ref, overflowed_id); |
| 2748 | } |
| 2749 | |
| 2750 | return try self.constructStruct( |
| 2751 | result_ty, |
| 2752 | &.{ operand_ty, ov_ty }, |
| 2753 | &.{ try wip_result.finalize(), try wip_ov.finalize() }, |
| 2754 | ); |
| 2755 | } |
| 2756 | |
| 2757 | fn airShlOverflow(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2758 | if (self.liveness.isUnused(inst)) return null; |
| 2759 | const mod = self.module; |
| 2760 | const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 2761 | const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; |
| 2762 | const lhs = try self.resolve(extra.lhs); |
| 2763 | const rhs = try self.resolve(extra.rhs); |
| 2764 | |
| 2765 | const result_ty = self.typeOfIndex(inst); |
| 2766 | const operand_ty = self.typeOf(extra.lhs); |
| 2767 | const shift_ty = self.typeOf(extra.rhs); |
| 2768 | const scalar_shift_ty_ref = try self.resolveType(shift_ty.scalarType(mod), .direct); |
| 2508 | 2769 | |
| 2509 | 2770 | const ov_ty = result_ty.structFieldType(1, self.module); |
| 2510 | | // Note: result is stored in a struct, so indirect representation. |
| 2511 | | const ov_ty_ref = try self.resolveType(ov_ty, .indirect); |
| 2512 | | |
| 2513 | | // TODO: Operations other than addition. |
| 2514 | | const value_id = self.spv.allocId(); |
| 2515 | | try self.func.body.emit(self.spv.gpa, add, .{ |
| 2516 | | .id_result_type = operand_ty_id, |
| 2517 | | .id_result = value_id, |
| 2518 | | .operand_1 = lhs, |
| 2519 | | .operand_2 = rhs, |
| 2520 | | }); |
| 2521 | 2771 | |
| 2522 | | const overflowed_id = switch (info.signedness) { |
| 2523 | | .unsigned => blk: { |
| 2524 | | // Overflow happened if the result is smaller than either of the operands. It doesn't matter which. |
| 2525 | | // For subtraction the conditions need to be swapped. |
| 2526 | | const overflowed_id = self.spv.allocId(); |
| 2527 | | try self.func.body.emit(self.spv.gpa, ucmp, .{ |
| 2528 | | .id_result_type = self.typeId(bool_ty_ref), |
| 2529 | | .id_result = overflowed_id, |
| 2530 | | .operand_1 = value_id, |
| 2531 | | .operand_2 = lhs, |
| 2532 | | }); |
| 2533 | | break :blk overflowed_id; |
| 2534 | | }, |
| 2535 | | .signed => blk: { |
| 2536 | | // lhs - rhs |
| 2537 | | // For addition, overflow happened if: |
| 2538 | | // - rhs is negative and value > lhs |
| 2539 | | // - rhs is positive and value < lhs |
| 2540 | | // This can be shortened to: |
| 2541 | | // (rhs < 0 and value > lhs) or (rhs >= 0 and value <= lhs) |
| 2542 | | // = (rhs < 0) == (value > lhs) |
| 2543 | | // = (rhs < 0) == (lhs < value) |
| 2544 | | // Note that signed overflow is also wrapping in spir-v. |
| 2545 | | // For subtraction, overflow happened if: |
| 2546 | | // - rhs is negative and value < lhs |
| 2547 | | // - rhs is positive and value > lhs |
| 2548 | | // This can be shortened to: |
| 2549 | | // (rhs < 0 and value < lhs) or (rhs >= 0 and value >= lhs) |
| 2550 | | // = (rhs < 0) == (value < lhs) |
| 2551 | | // = (rhs < 0) == (lhs > value) |
| 2552 | | |
| 2553 | | const rhs_lt_zero_id = self.spv.allocId(); |
| 2554 | | const zero_id = try self.constInt(operand_ty_ref, 0); |
| 2555 | | try self.func.body.emit(self.spv.gpa, .OpSLessThan, .{ |
| 2556 | | .id_result_type = self.typeId(bool_ty_ref), |
| 2557 | | .id_result = rhs_lt_zero_id, |
| 2558 | | .operand_1 = rhs, |
| 2559 | | .operand_2 = zero_id, |
| 2560 | | }); |
| 2772 | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 2561 | 2773 | |
| 2562 | | const value_gt_lhs_id = self.spv.allocId(); |
| 2563 | | try self.func.body.emit(self.spv.gpa, scmp, .{ |
| 2564 | | .id_result_type = self.typeId(bool_ty_ref), |
| 2565 | | .id_result = value_gt_lhs_id, |
| 2566 | | .operand_1 = lhs, |
| 2567 | | .operand_2 = value_id, |
| 2568 | | }); |
| 2774 | const info = self.arithmeticTypeInfo(operand_ty); |
| 2775 | switch (info.class) { |
| 2776 | .composite_integer => return self.todo("overflow shift for composite integers", .{}), |
| 2777 | .integer, .strange_integer => {}, |
| 2778 | .float, .bool => unreachable, |
| 2779 | } |
| 2569 | 2780 | |
| 2570 | | const overflowed_id = self.spv.allocId(); |
| 2571 | | try self.func.body.emit(self.spv.gpa, .OpLogicalEqual, .{ |
| 2572 | | .id_result_type = self.typeId(bool_ty_ref), |
| 2573 | | .id_result = overflowed_id, |
| 2574 | | .operand_1 = rhs_lt_zero_id, |
| 2575 | | .operand_2 = value_gt_lhs_id, |
| 2781 | var wip_result = try self.elementWise(operand_ty); |
| 2782 | defer wip_result.deinit(); |
| 2783 | var wip_ov = try self.elementWise(ov_ty); |
| 2784 | defer wip_ov.deinit(); |
| 2785 | for (wip_result.results, wip_ov.results, 0..) |*result_id, *ov_id, i| { |
| 2786 | const lhs_elem_id = try wip_result.elementAt(operand_ty, lhs, i); |
| 2787 | const rhs_elem_id = try wip_result.elementAt(shift_ty, rhs, i); |
| 2788 | |
| 2789 | // Sometimes Zig doesn't make both of the arguments the same types here. SPIR-V expects that, |
| 2790 | // so just manually upcast it if required. |
| 2791 | const shift_id = if (scalar_shift_ty_ref != wip_result.scalar_ty_ref) blk: { |
| 2792 | const shift_id = self.spv.allocId(); |
| 2793 | try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ |
| 2794 | .id_result_type = wip_result.scalar_ty_id, |
| 2795 | .id_result = shift_id, |
| 2796 | .unsigned_value = rhs_elem_id, |
| 2576 | 2797 | }); |
| 2577 | | break :blk overflowed_id; |
| 2578 | | }, |
| 2579 | | }; |
| 2798 | break :blk shift_id; |
| 2799 | } else rhs_elem_id; |
| 2800 | |
| 2801 | const value_id = self.spv.allocId(); |
| 2802 | try self.func.body.emit(self.spv.gpa, .OpShiftLeftLogical, .{ |
| 2803 | .id_result_type = wip_result.scalar_ty_id, |
| 2804 | .id_result = value_id, |
| 2805 | .base = lhs_elem_id, |
| 2806 | .shift = shift_id, |
| 2807 | }); |
| 2808 | result_id.* = try self.normalize(wip_result.scalar_ty_ref, value_id, info); |
| 2809 | |
| 2810 | const right_shift_id = self.spv.allocId(); |
| 2811 | switch (info.signedness) { |
| 2812 | .signed => { |
| 2813 | try self.func.body.emit(self.spv.gpa, .OpShiftRightArithmetic, .{ |
| 2814 | .id_result_type = wip_result.scalar_ty_id, |
| 2815 | .id_result = right_shift_id, |
| 2816 | .base = result_id.*, |
| 2817 | .shift = shift_id, |
| 2818 | }); |
| 2819 | }, |
| 2820 | .unsigned => { |
| 2821 | try self.func.body.emit(self.spv.gpa, .OpShiftRightLogical, .{ |
| 2822 | .id_result_type = wip_result.scalar_ty_id, |
| 2823 | .id_result = right_shift_id, |
| 2824 | .base = result_id.*, |
| 2825 | .shift = shift_id, |
| 2826 | }); |
| 2827 | }, |
| 2828 | } |
| 2829 | |
| 2830 | const overflowed_id = self.spv.allocId(); |
| 2831 | try self.func.body.emit(self.spv.gpa, .OpINotEqual, .{ |
| 2832 | .id_result_type = self.typeId(bool_ty_ref), |
| 2833 | .id_result = overflowed_id, |
| 2834 | .operand_1 = lhs_elem_id, |
| 2835 | .operand_2 = right_shift_id, |
| 2836 | }); |
| 2837 | |
| 2838 | ov_id.* = try self.intFromBool(wip_ov.scalar_ty_ref, overflowed_id); |
| 2839 | } |
| 2580 | 2840 | |
| 2581 | | // Construct the struct that Zig wants as result. |
| 2582 | | // The value should already be the correct type. |
| 2583 | | const ov_id = try self.intFromBool(ov_ty_ref, overflowed_id); |
| 2584 | 2841 | return try self.constructStruct( |
| 2585 | 2842 | result_ty, |
| 2586 | 2843 | &.{ operand_ty, ov_ty }, |
| 2587 | | &.{ value_id, ov_id }, |
| 2844 | &.{ try wip_result.finalize(), try wip_ov.finalize() }, |
| 2588 | 2845 | ); |
| 2589 | 2846 | } |
| 2590 | 2847 | |
| 2848 | fn airMulAdd(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2849 | if (self.liveness.isUnused(inst)) return null; |
| 2850 | |
| 2851 | const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; |
| 2852 | const extra = self.air.extraData(Air.Bin, pl_op.payload).data; |
| 2853 | |
| 2854 | const mulend1 = try self.resolve(extra.lhs); |
| 2855 | const mulend2 = try self.resolve(extra.rhs); |
| 2856 | const addend = try self.resolve(pl_op.operand); |
| 2857 | |
| 2858 | const ty = self.typeOfIndex(inst); |
| 2859 | |
| 2860 | const info = self.arithmeticTypeInfo(ty); |
| 2861 | assert(info.class == .float); // .mul_add is only emitted for floats |
| 2862 | |
| 2863 | var wip = try self.elementWise(ty); |
| 2864 | defer wip.deinit(); |
| 2865 | for (0..wip.results.len) |i| { |
| 2866 | const mul_result = self.spv.allocId(); |
| 2867 | try self.func.body.emit(self.spv.gpa, .OpFMul, .{ |
| 2868 | .id_result_type = wip.scalar_ty_id, |
| 2869 | .id_result = mul_result, |
| 2870 | .operand_1 = try wip.elementAt(ty, mulend1, i), |
| 2871 | .operand_2 = try wip.elementAt(ty, mulend2, i), |
| 2872 | }); |
| 2873 | |
| 2874 | try self.func.body.emit(self.spv.gpa, .OpFAdd, .{ |
| 2875 | .id_result_type = wip.scalar_ty_id, |
| 2876 | .id_result = wip.allocId(i), |
| 2877 | .operand_1 = mul_result, |
| 2878 | .operand_2 = try wip.elementAt(ty, addend, i), |
| 2879 | }); |
| 2880 | } |
| 2881 | return try wip.finalize(); |
| 2882 | } |
| 2883 | |
| 2884 | fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2885 | if (self.liveness.isUnused(inst)) return null; |
| 2886 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 2887 | const operand_id = try self.resolve(ty_op.operand); |
| 2888 | const result_ty = self.typeOfIndex(inst); |
| 2889 | var wip = try self.elementWise(result_ty); |
| 2890 | defer wip.deinit(); |
| 2891 | for (wip.results) |*result_id| { |
| 2892 | result_id.* = operand_id; |
| 2893 | } |
| 2894 | return try wip.finalize(); |
| 2895 | } |
| 2896 | |
| 2897 | fn airReduce(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2898 | if (self.liveness.isUnused(inst)) return null; |
| 2899 | const mod = self.module; |
| 2900 | const reduce = self.air.instructions.items(.data)[@intFromEnum(inst)].reduce; |
| 2901 | const operand = try self.resolve(reduce.operand); |
| 2902 | const operand_ty = self.typeOf(reduce.operand); |
| 2903 | const scalar_ty = operand_ty.scalarType(mod); |
| 2904 | const scalar_ty_ref = try self.resolveType(scalar_ty, .direct); |
| 2905 | const scalar_ty_id = self.typeId(scalar_ty_ref); |
| 2906 | |
| 2907 | const info = self.arithmeticTypeInfo(operand_ty); |
| 2908 | |
| 2909 | var result_id = try self.extractField(scalar_ty, operand, 0); |
| 2910 | const len = operand_ty.vectorLen(mod); |
| 2911 | |
| 2912 | switch (reduce.operation) { |
| 2913 | .Min, .Max => |op| { |
| 2914 | const cmp_op: std.math.CompareOperator = if (op == .Max) .gt else .lt; |
| 2915 | for (1..len) |i| { |
| 2916 | const lhs = result_id; |
| 2917 | const rhs = try self.extractField(scalar_ty, operand, @intCast(i)); |
| 2918 | result_id = try self.minMax(scalar_ty, cmp_op, lhs, rhs); |
| 2919 | } |
| 2920 | |
| 2921 | return result_id; |
| 2922 | }, |
| 2923 | else => {}, |
| 2924 | } |
| 2925 | |
| 2926 | const opcode: Opcode = switch (info.class) { |
| 2927 | .bool => switch (reduce.operation) { |
| 2928 | .And => .OpLogicalAnd, |
| 2929 | .Or => .OpLogicalOr, |
| 2930 | .Xor => .OpLogicalNotEqual, |
| 2931 | else => unreachable, |
| 2932 | }, |
| 2933 | .strange_integer, .integer => switch (reduce.operation) { |
| 2934 | .And => .OpBitwiseAnd, |
| 2935 | .Or => .OpBitwiseOr, |
| 2936 | .Xor => .OpBitwiseXor, |
| 2937 | .Add => .OpIAdd, |
| 2938 | .Mul => .OpIMul, |
| 2939 | else => unreachable, |
| 2940 | }, |
| 2941 | .float => switch (reduce.operation) { |
| 2942 | .Add => .OpFAdd, |
| 2943 | .Mul => .OpFMul, |
| 2944 | else => unreachable, |
| 2945 | }, |
| 2946 | .composite_integer => unreachable, // TODO |
| 2947 | }; |
| 2948 | |
| 2949 | for (1..len) |i| { |
| 2950 | const lhs = result_id; |
| 2951 | const rhs = try self.extractField(scalar_ty, operand, @intCast(i)); |
| 2952 | result_id = self.spv.allocId(); |
| 2953 | |
| 2954 | try self.func.body.emitRaw(self.spv.gpa, opcode, 4); |
| 2955 | self.func.body.writeOperand(spec.IdResultType, scalar_ty_id); |
| 2956 | self.func.body.writeOperand(spec.IdResult, result_id); |
| 2957 | self.func.body.writeOperand(spec.IdResultType, lhs); |
| 2958 | self.func.body.writeOperand(spec.IdResultType, rhs); |
| 2959 | } |
| 2960 | |
| 2961 | return result_id; |
| 2962 | } |
| 2963 | |
| 2591 | 2964 | fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 2592 | 2965 | const mod = self.module; |
| 2593 | 2966 | if (self.liveness.isUnused(inst)) return null; |
| 2594 | | const ty = self.typeOfIndex(inst); |
| 2595 | 2967 | const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; |
| 2596 | 2968 | const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data; |
| 2597 | 2969 | const a = try self.resolve(extra.a); |
| 2598 | 2970 | const b = try self.resolve(extra.b); |
| 2599 | 2971 | const mask = Value.fromInterned(extra.mask); |
| 2600 | | const mask_len = extra.mask_len; |
| 2601 | | const a_len = self.typeOf(extra.a).vectorLen(mod); |
| 2602 | 2972 | |
| 2603 | | const result_id = self.spv.allocId(); |
| 2604 | | const result_type_id = try self.resolveTypeId(ty); |
| 2605 | | // Similar to LLVM, SPIR-V uses indices larger than the length of the first vector |
| 2606 | | // to index into the second vector. |
| 2607 | | try self.func.body.emitRaw(self.spv.gpa, .OpVectorShuffle, 4 + mask_len); |
| 2608 | | self.func.body.writeOperand(spec.IdResultType, result_type_id); |
| 2609 | | self.func.body.writeOperand(spec.IdResult, result_id); |
| 2610 | | self.func.body.writeOperand(spec.IdRef, a); |
| 2611 | | self.func.body.writeOperand(spec.IdRef, b); |
| 2973 | const ty = self.typeOfIndex(inst); |
| 2612 | 2974 | |
| 2613 | | var i: usize = 0; |
| 2614 | | while (i < mask_len) : (i += 1) { |
| 2975 | var wip = try self.elementWise(ty); |
| 2976 | defer wip.deinit(); |
| 2977 | for (wip.results, 0..) |*result_id, i| { |
| 2615 | 2978 | const elem = try mask.elemValue(mod, i); |
| 2616 | 2979 | if (elem.isUndef(mod)) { |
| 2617 | | self.func.body.writeOperand(spec.LiteralInteger, 0xFFFF_FFFF); |
| 2980 | result_id.* = try self.spv.constUndef(wip.scalar_ty_ref); |
| 2981 | continue; |
| 2982 | } |
| 2983 | |
| 2984 | const index = elem.toSignedInt(mod); |
| 2985 | if (index >= 0) { |
| 2986 | result_id.* = try self.extractField(wip.scalar_ty, a, @intCast(index)); |
| 2618 | 2987 | } else { |
| 2619 | | const int = elem.toSignedInt(mod); |
| 2620 | | const unsigned = if (int >= 0) @as(u32, @intCast(int)) else @as(u32, @intCast(~int + a_len)); |
| 2621 | | self.func.body.writeOperand(spec.LiteralInteger, unsigned); |
| 2988 | result_id.* = try self.extractField(wip.scalar_ty, b, @intCast(~index)); |
| 2622 | 2989 | } |
| 2623 | 2990 | } |
| 2624 | | return result_id; |
| 2991 | return try wip.finalize(); |
| 2625 | 2992 | } |
| 2626 | 2993 | |
| 2627 | 2994 | fn indicesToIds(self: *DeclGen, indices: []const u32) ![]IdRef { |
| ... | ... | @@ -2828,26 +3195,21 @@ const DeclGen = struct { |
| 2828 | 3195 | return result_id; |
| 2829 | 3196 | }, |
| 2830 | 3197 | .Vector => { |
| 2831 | | const child_ty = ty.childType(mod); |
| 2832 | | const vector_len = ty.vectorLen(mod); |
| 2833 | | |
| 2834 | | const constituents = try self.gpa.alloc(IdRef, vector_len); |
| 2835 | | defer self.gpa.free(constituents); |
| 2836 | | |
| 2837 | | for (constituents, 0..) |*constituent, i| { |
| 2838 | | const lhs_index_id = try self.extractField(child_ty, cmp_lhs_id, @intCast(i)); |
| 2839 | | const rhs_index_id = try self.extractField(child_ty, cmp_rhs_id, @intCast(i)); |
| 2840 | | const result_id = try self.cmp(op, Type.bool, child_ty, lhs_index_id, rhs_index_id); |
| 2841 | | constituent.* = try self.convertToIndirect(Type.bool, result_id); |
| 3198 | var wip = try self.elementWise(result_ty); |
| 3199 | defer wip.deinit(); |
| 3200 | const scalar_ty = ty.scalarType(mod); |
| 3201 | for (wip.results, 0..) |*result_id, i| { |
| 3202 | const lhs_elem_id = try wip.elementAt(ty, lhs_id, i); |
| 3203 | const rhs_elem_id = try wip.elementAt(ty, rhs_id, i); |
| 3204 | result_id.* = try self.cmp(op, Type.bool, scalar_ty, lhs_elem_id, rhs_elem_id); |
| 2842 | 3205 | } |
| 2843 | | |
| 2844 | | return try self.constructArray(result_ty, constituents); |
| 3206 | return wip.finalize(); |
| 2845 | 3207 | }, |
| 2846 | 3208 | else => unreachable, |
| 2847 | 3209 | }; |
| 2848 | 3210 | |
| 2849 | 3211 | const opcode: Opcode = opcode: { |
| 2850 | | const info = try self.arithmeticTypeInfo(op_ty); |
| 3212 | const info = self.arithmeticTypeInfo(op_ty); |
| 2851 | 3213 | const signedness = switch (info.class) { |
| 2852 | 3214 | .composite_integer => { |
| 2853 | 3215 | return self.todo("binary operations for composite integers", .{}); |
| ... | ... | @@ -2865,14 +3227,7 @@ const DeclGen = struct { |
| 2865 | 3227 | .neq => .OpLogicalNotEqual, |
| 2866 | 3228 | else => unreachable, |
| 2867 | 3229 | }, |
| 2868 | | .strange_integer => sign: { |
| 2869 | | const op_ty_ref = try self.resolveType(op_ty, .direct); |
| 2870 | | // Mask operands before performing comparison. |
| 2871 | | cmp_lhs_id = try self.normalizeInt(op_ty_ref, cmp_lhs_id, info); |
| 2872 | | cmp_rhs_id = try self.normalizeInt(op_ty_ref, cmp_rhs_id, info); |
| 2873 | | break :sign info.signedness; |
| 2874 | | }, |
| 2875 | | .integer => info.signedness, |
| 3230 | .integer, .strange_integer => info.signedness, |
| 2876 | 3231 | }; |
| 2877 | 3232 | |
| 2878 | 3233 | break :opcode switch (signedness) { |
| ... | ... | @@ -2942,50 +3297,64 @@ const DeclGen = struct { |
| 2942 | 3297 | const mod = self.module; |
| 2943 | 3298 | const src_ty_ref = try self.resolveType(src_ty, .direct); |
| 2944 | 3299 | const dst_ty_ref = try self.resolveType(dst_ty, .direct); |
| 2945 | | if (src_ty_ref == dst_ty_ref) { |
| 2946 | | return src_id; |
| 2947 | | } |
| 3300 | const src_key = self.spv.cache.lookup(src_ty_ref); |
| 3301 | const dst_key = self.spv.cache.lookup(dst_ty_ref); |
| 2948 | 3302 | |
| 2949 | | // TODO: Some more cases are missing here |
| 2950 | | // See fn bitCast in llvm.zig |
| 3303 | const result_id = blk: { |
| 3304 | if (src_ty_ref == dst_ty_ref) { |
| 3305 | break :blk src_id; |
| 3306 | } |
| 2951 | 3307 | |
| 2952 | | if (src_ty.zigTypeTag(mod) == .Int and dst_ty.isPtrAtRuntime(mod)) { |
| 2953 | | const result_id = self.spv.allocId(); |
| 2954 | | try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{ |
| 2955 | | .id_result_type = self.typeId(dst_ty_ref), |
| 2956 | | .id_result = result_id, |
| 2957 | | .integer_value = src_id, |
| 2958 | | }); |
| 2959 | | return result_id; |
| 2960 | | } |
| 3308 | // TODO: Some more cases are missing here |
| 3309 | // See fn bitCast in llvm.zig |
| 2961 | 3310 | |
| 2962 | | // We can only use OpBitcast for specific conversions: between numerical types, and |
| 2963 | | // between pointers. If the resolved spir-v types fall into this category then emit OpBitcast, |
| 2964 | | // otherwise use a temporary and perform a pointer cast. |
| 2965 | | const src_key = self.spv.cache.lookup(src_ty_ref); |
| 2966 | | const dst_key = self.spv.cache.lookup(dst_ty_ref); |
| 3311 | if (src_ty.zigTypeTag(mod) == .Int and dst_ty.isPtrAtRuntime(mod)) { |
| 3312 | const result_id = self.spv.allocId(); |
| 3313 | try self.func.body.emit(self.spv.gpa, .OpConvertUToPtr, .{ |
| 3314 | .id_result_type = self.typeId(dst_ty_ref), |
| 3315 | .id_result = result_id, |
| 3316 | .integer_value = src_id, |
| 3317 | }); |
| 3318 | break :blk result_id; |
| 3319 | } |
| 2967 | 3320 | |
| 2968 | | if ((src_key.isNumericalType() and dst_key.isNumericalType()) or (src_key == .ptr_type and dst_key == .ptr_type)) { |
| 2969 | | const result_id = self.spv.allocId(); |
| 3321 | // We can only use OpBitcast for specific conversions: between numerical types, and |
| 3322 | // between pointers. If the resolved spir-v types fall into this category then emit OpBitcast, |
| 3323 | // otherwise use a temporary and perform a pointer cast. |
| 3324 | if ((src_key.isNumericalType() and dst_key.isNumericalType()) or (src_key == .ptr_type and dst_key == .ptr_type)) { |
| 3325 | const result_id = self.spv.allocId(); |
| 3326 | try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ |
| 3327 | .id_result_type = self.typeId(dst_ty_ref), |
| 3328 | .id_result = result_id, |
| 3329 | .operand = src_id, |
| 3330 | }); |
| 3331 | |
| 3332 | break :blk result_id; |
| 3333 | } |
| 3334 | |
| 3335 | const dst_ptr_ty_ref = try self.ptrType(dst_ty, .Function); |
| 3336 | |
| 3337 | const tmp_id = try self.alloc(src_ty, .{ .storage_class = .Function }); |
| 3338 | try self.store(src_ty, tmp_id, src_id, .{}); |
| 3339 | const casted_ptr_id = self.spv.allocId(); |
| 2970 | 3340 | try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ |
| 2971 | | .id_result_type = self.typeId(dst_ty_ref), |
| 2972 | | .id_result = result_id, |
| 2973 | | .operand = src_id, |
| 3341 | .id_result_type = self.typeId(dst_ptr_ty_ref), |
| 3342 | .id_result = casted_ptr_id, |
| 3343 | .operand = tmp_id, |
| 2974 | 3344 | }); |
| 2975 | | return result_id; |
| 2976 | | } |
| 3345 | break :blk try self.load(dst_ty, casted_ptr_id, .{}); |
| 3346 | }; |
| 2977 | 3347 | |
| 2978 | | const dst_ptr_ty_ref = try self.ptrType(dst_ty, .Function); |
| 3348 | // Because strange integers use sign-extended representation, we may need to normalize |
| 3349 | // the result here. |
| 3350 | // TODO: This detail could cause stuff like @as(*const i1, @ptrCast(&@as(u1, 1))) to break |
| 3351 | // should we change the representation of strange integers? |
| 3352 | if (dst_ty.zigTypeTag(mod) == .Int) { |
| 3353 | const info = self.arithmeticTypeInfo(dst_ty); |
| 3354 | return try self.normalize(dst_ty_ref, result_id, info); |
| 3355 | } |
| 2979 | 3356 | |
| 2980 | | const tmp_id = try self.alloc(src_ty, .{ .storage_class = .Function }); |
| 2981 | | try self.store(src_ty, tmp_id, src_id, .{}); |
| 2982 | | const casted_ptr_id = self.spv.allocId(); |
| 2983 | | try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ |
| 2984 | | .id_result_type = self.typeId(dst_ptr_ty_ref), |
| 2985 | | .id_result = casted_ptr_id, |
| 2986 | | .operand = tmp_id, |
| 2987 | | }); |
| 2988 | | return try self.load(dst_ty, casted_ptr_id, .{}); |
| 3357 | return result_id; |
| 2989 | 3358 | } |
| 2990 | 3359 | |
| 2991 | 3360 | fn airBitCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| ... | ... | @@ -3004,34 +3373,43 @@ const DeclGen = struct { |
| 3004 | 3373 | const operand_id = try self.resolve(ty_op.operand); |
| 3005 | 3374 | const src_ty = self.typeOf(ty_op.operand); |
| 3006 | 3375 | const dst_ty = self.typeOfIndex(inst); |
| 3007 | | const src_ty_ref = try self.resolveType(src_ty, .direct); |
| 3008 | | const dst_ty_ref = try self.resolveType(dst_ty, .direct); |
| 3009 | | |
| 3010 | | const src_info = try self.arithmeticTypeInfo(src_ty); |
| 3011 | | const dst_info = try self.arithmeticTypeInfo(dst_ty); |
| 3012 | 3376 | |
| 3013 | | // While intcast promises that the value already fits, the upper bits of a |
| 3014 | | // strange integer may contain garbage. Therefore, mask/sign extend it before. |
| 3015 | | const src_id = try self.normalizeInt(src_ty_ref, operand_id, src_info); |
| 3377 | const src_info = self.arithmeticTypeInfo(src_ty); |
| 3378 | const dst_info = self.arithmeticTypeInfo(dst_ty); |
| 3016 | 3379 | |
| 3017 | 3380 | if (src_info.backing_bits == dst_info.backing_bits) { |
| 3018 | | return src_id; |
| 3381 | return operand_id; |
| 3019 | 3382 | } |
| 3020 | 3383 | |
| 3021 | | const result_id = self.spv.allocId(); |
| 3022 | | switch (dst_info.signedness) { |
| 3023 | | .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ |
| 3024 | | .id_result_type = self.typeId(dst_ty_ref), |
| 3025 | | .id_result = result_id, |
| 3026 | | .signed_value = src_id, |
| 3027 | | }), |
| 3028 | | .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ |
| 3029 | | .id_result_type = self.typeId(dst_ty_ref), |
| 3030 | | .id_result = result_id, |
| 3031 | | .unsigned_value = src_id, |
| 3032 | | }), |
| 3384 | var wip = try self.elementWise(dst_ty); |
| 3385 | defer wip.deinit(); |
| 3386 | for (wip.results, 0..) |*result_id, i| { |
| 3387 | const elem_id = try wip.elementAt(src_ty, operand_id, i); |
| 3388 | const value_id = self.spv.allocId(); |
| 3389 | switch (dst_info.signedness) { |
| 3390 | .signed => try self.func.body.emit(self.spv.gpa, .OpSConvert, .{ |
| 3391 | .id_result_type = wip.scalar_ty_id, |
| 3392 | .id_result = value_id, |
| 3393 | .signed_value = elem_id, |
| 3394 | }), |
| 3395 | .unsigned => try self.func.body.emit(self.spv.gpa, .OpUConvert, .{ |
| 3396 | .id_result_type = wip.scalar_ty_id, |
| 3397 | .id_result = value_id, |
| 3398 | .unsigned_value = elem_id, |
| 3399 | }), |
| 3400 | } |
| 3401 | |
| 3402 | // Make sure to normalize the result if shrinking. |
| 3403 | // Because strange ints are sign extended in their backing |
| 3404 | // type, we don't need to normalize when growing the type. The |
| 3405 | // representation is already the same. |
| 3406 | if (dst_info.bits < src_info.bits) { |
| 3407 | result_id.* = try self.normalize(wip.scalar_ty_ref, value_id, dst_info); |
| 3408 | } else { |
| 3409 | result_id.* = value_id; |
| 3410 | } |
| 3033 | 3411 | } |
| 3034 | | return result_id; |
| 3412 | return try wip.finalize(); |
| 3035 | 3413 | } |
| 3036 | 3414 | |
| 3037 | 3415 | fn intFromPtr(self: *DeclGen, operand_id: IdRef) !IdRef { |
| ... | ... | @@ -3059,7 +3437,7 @@ const DeclGen = struct { |
| 3059 | 3437 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 3060 | 3438 | const operand_ty = self.typeOf(ty_op.operand); |
| 3061 | 3439 | const operand_id = try self.resolve(ty_op.operand); |
| 3062 | | const operand_info = try self.arithmeticTypeInfo(operand_ty); |
| 3440 | const operand_info = self.arithmeticTypeInfo(operand_ty); |
| 3063 | 3441 | const dest_ty = self.typeOfIndex(inst); |
| 3064 | 3442 | const dest_ty_id = try self.resolveTypeId(dest_ty); |
| 3065 | 3443 | |
| ... | ... | @@ -3085,7 +3463,7 @@ const DeclGen = struct { |
| 3085 | 3463 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 3086 | 3464 | const operand_id = try self.resolve(ty_op.operand); |
| 3087 | 3465 | const dest_ty = self.typeOfIndex(inst); |
| 3088 | | const dest_info = try self.arithmeticTypeInfo(dest_ty); |
| 3466 | const dest_info = self.arithmeticTypeInfo(dest_ty); |
| 3089 | 3467 | const dest_ty_id = try self.resolveTypeId(dest_ty); |
| 3090 | 3468 | |
| 3091 | 3469 | const result_id = self.spv.allocId(); |
| ... | ... | @@ -3104,6 +3482,22 @@ const DeclGen = struct { |
| 3104 | 3482 | return result_id; |
| 3105 | 3483 | } |
| 3106 | 3484 | |
| 3485 | fn airIntFromBool(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 3486 | if (self.liveness.isUnused(inst)) return null; |
| 3487 | |
| 3488 | const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; |
| 3489 | const operand_id = try self.resolve(un_op); |
| 3490 | const result_ty = self.typeOfIndex(inst); |
| 3491 | |
| 3492 | var wip = try self.elementWise(result_ty); |
| 3493 | defer wip.deinit(); |
| 3494 | for (wip.results, 0..) |*result_id, i| { |
| 3495 | const elem_id = try wip.elementAt(Type.bool, operand_id, i); |
| 3496 | result_id.* = try self.intFromBool(wip.scalar_ty_ref, elem_id); |
| 3497 | } |
| 3498 | return try wip.finalize(); |
| 3499 | } |
| 3500 | |
| 3107 | 3501 | fn airFloatCast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 3108 | 3502 | if (self.liveness.isUnused(inst)) return null; |
| 3109 | 3503 | |
| ... | ... | @@ -3126,31 +3520,31 @@ const DeclGen = struct { |
| 3126 | 3520 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 3127 | 3521 | const operand_id = try self.resolve(ty_op.operand); |
| 3128 | 3522 | const result_ty = self.typeOfIndex(inst); |
| 3129 | | const result_ty_id = try self.resolveTypeId(result_ty); |
| 3130 | | const info = try self.arithmeticTypeInfo(result_ty); |
| 3523 | const info = self.arithmeticTypeInfo(result_ty); |
| 3131 | 3524 | |
| 3132 | | const result_id = self.spv.allocId(); |
| 3133 | | switch (info.class) { |
| 3134 | | .bool => { |
| 3135 | | try self.func.body.emit(self.spv.gpa, .OpLogicalNot, .{ |
| 3136 | | .id_result_type = result_ty_id, |
| 3137 | | .id_result = result_id, |
| 3138 | | .operand = operand_id, |
| 3139 | | }); |
| 3140 | | }, |
| 3141 | | .float => unreachable, |
| 3142 | | .composite_integer => unreachable, // TODO |
| 3143 | | .strange_integer, .integer => { |
| 3144 | | // Note: strange integer bits will be masked before operations that do not hold under modulo. |
| 3145 | | try self.func.body.emit(self.spv.gpa, .OpNot, .{ |
| 3146 | | .id_result_type = result_ty_id, |
| 3147 | | .id_result = result_id, |
| 3148 | | .operand = operand_id, |
| 3149 | | }); |
| 3150 | | }, |
| 3525 | var wip = try self.elementWise(result_ty); |
| 3526 | defer wip.deinit(); |
| 3527 | |
| 3528 | for (0..wip.results.len) |i| { |
| 3529 | const args = .{ |
| 3530 | .id_result_type = wip.scalar_ty_id, |
| 3531 | .id_result = wip.allocId(i), |
| 3532 | .operand = try wip.elementAt(result_ty, operand_id, i), |
| 3533 | }; |
| 3534 | switch (info.class) { |
| 3535 | .bool => { |
| 3536 | try self.func.body.emit(self.spv.gpa, .OpLogicalNot, args); |
| 3537 | }, |
| 3538 | .float => unreachable, |
| 3539 | .composite_integer => unreachable, // TODO |
| 3540 | .strange_integer, .integer => { |
| 3541 | // Note: strange integer bits will be masked before operations that do not hold under modulo. |
| 3542 | try self.func.body.emit(self.spv.gpa, .OpNot, args); |
| 3543 | }, |
| 3544 | } |
| 3151 | 3545 | } |
| 3152 | 3546 | |
| 3153 | | return result_id; |
| 3547 | return try wip.finalize(); |
| 3154 | 3548 | } |
| 3155 | 3549 | |
| 3156 | 3550 | fn airArrayToSlice(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| ... | ... | @@ -3213,7 +3607,6 @@ const DeclGen = struct { |
| 3213 | 3607 | const elements: []const Air.Inst.Ref = @ptrCast(self.air.extra[ty_pl.payload..][0..len]); |
| 3214 | 3608 | |
| 3215 | 3609 | switch (result_ty.zigTypeTag(mod)) { |
| 3216 | | .Vector => unreachable, // TODO |
| 3217 | 3610 | .Struct => { |
| 3218 | 3611 | if (mod.typeToPackedStruct(result_ty)) |struct_type| { |
| 3219 | 3612 | _ = struct_type; |
| ... | ... | @@ -3261,7 +3654,7 @@ const DeclGen = struct { |
| 3261 | 3654 | constituents[0..index], |
| 3262 | 3655 | ); |
| 3263 | 3656 | }, |
| 3264 | | .Array => { |
| 3657 | .Vector, .Array => { |
| 3265 | 3658 | const array_info = result_ty.arrayInfo(mod); |
| 3266 | 3659 | const n_elems: usize = @intCast(result_ty.arrayLenIncludingSentinel(mod)); |
| 3267 | 3660 | const elem_ids = try self.gpa.alloc(IdRef, n_elems); |
| ... | ... | @@ -3433,6 +3826,28 @@ const DeclGen = struct { |
| 3433 | 3826 | return try self.load(elem_ty, elem_ptr_id, .{ .is_volatile = ptr_ty.isVolatilePtr(mod) }); |
| 3434 | 3827 | } |
| 3435 | 3828 | |
| 3829 | fn airVectorStoreElem(self: *DeclGen, inst: Air.Inst.Index) !void { |
| 3830 | const mod = self.module; |
| 3831 | const data = self.air.instructions.items(.data)[@intFromEnum(inst)].vector_store_elem; |
| 3832 | const extra = self.air.extraData(Air.Bin, data.payload).data; |
| 3833 | |
| 3834 | const vector_ptr_ty = self.typeOf(data.vector_ptr); |
| 3835 | const vector_ty = vector_ptr_ty.childType(mod); |
| 3836 | const scalar_ty = vector_ty.scalarType(mod); |
| 3837 | |
| 3838 | const storage_class = spvStorageClass(vector_ptr_ty.ptrAddressSpace(mod)); |
| 3839 | const scalar_ptr_ty_ref = try self.ptrType(scalar_ty, storage_class); |
| 3840 | |
| 3841 | const vector_ptr = try self.resolve(data.vector_ptr); |
| 3842 | const index = try self.resolve(extra.lhs); |
| 3843 | const operand = try self.resolve(extra.rhs); |
| 3844 | |
| 3845 | const elem_ptr_id = try self.accessChainId(scalar_ptr_ty_ref, vector_ptr, &.{index}); |
| 3846 | try self.store(scalar_ty, elem_ptr_id, operand, .{ |
| 3847 | .is_volatile = vector_ptr_ty.isVolatilePtr(mod), |
| 3848 | }); |
| 3849 | } |
| 3850 | |
| 3436 | 3851 | fn airSetUnionTag(self: *DeclGen, inst: Air.Inst.Index) !void { |
| 3437 | 3852 | const mod = self.module; |
| 3438 | 3853 | const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| ... | ... | @@ -4424,20 +4839,24 @@ const DeclGen = struct { |
| 4424 | 4839 | return try self.constructStruct(err_union_ty, &types, &members); |
| 4425 | 4840 | } |
| 4426 | 4841 | |
| 4427 | | fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, pred: enum { is_null, is_non_null }) !?IdRef { |
| 4842 | fn airIsNull(self: *DeclGen, inst: Air.Inst.Index, is_pointer: bool, pred: enum { is_null, is_non_null }) !?IdRef { |
| 4428 | 4843 | if (self.liveness.isUnused(inst)) return null; |
| 4429 | 4844 | |
| 4430 | 4845 | const mod = self.module; |
| 4431 | 4846 | const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op; |
| 4432 | 4847 | const operand_id = try self.resolve(un_op); |
| 4433 | | const optional_ty = self.typeOf(un_op); |
| 4434 | | |
| 4848 | const operand_ty = self.typeOf(un_op); |
| 4849 | const optional_ty = if (is_pointer) operand_ty.childType(mod) else operand_ty; |
| 4435 | 4850 | const payload_ty = optional_ty.optionalChild(mod); |
| 4436 | 4851 | |
| 4437 | 4852 | const bool_ty_ref = try self.resolveType(Type.bool, .direct); |
| 4438 | 4853 | |
| 4439 | 4854 | if (optional_ty.optionalReprIsPayload(mod)) { |
| 4440 | 4855 | // Pointer payload represents nullability: pointer or slice. |
| 4856 | const loaded_id = if (is_pointer) |
| 4857 | try self.load(optional_ty, operand_id, .{}) |
| 4858 | else |
| 4859 | operand_id; |
| 4441 | 4860 | |
| 4442 | 4861 | const ptr_ty = if (payload_ty.isSlice(mod)) |
| 4443 | 4862 | payload_ty.slicePtrFieldType(mod) |
| ... | ... | @@ -4445,9 +4864,9 @@ const DeclGen = struct { |
| 4445 | 4864 | payload_ty; |
| 4446 | 4865 | |
| 4447 | 4866 | const ptr_id = if (payload_ty.isSlice(mod)) |
| 4448 | | try self.extractField(ptr_ty, operand_id, 0) |
| 4867 | try self.extractField(ptr_ty, loaded_id, 0) |
| 4449 | 4868 | else |
| 4450 | | operand_id; |
| 4869 | loaded_id; |
| 4451 | 4870 | |
| 4452 | 4871 | const payload_ty_ref = try self.resolveType(ptr_ty, .direct); |
| 4453 | 4872 | const null_id = try self.spv.constNull(payload_ty_ref); |
| ... | ... | @@ -4458,13 +4877,26 @@ const DeclGen = struct { |
| 4458 | 4877 | return try self.cmp(op, Type.bool, ptr_ty, ptr_id, null_id); |
| 4459 | 4878 | } |
| 4460 | 4879 | |
| 4461 | | const is_non_null_id = if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) |
| 4462 | | try self.extractField(Type.bool, operand_id, 1) |
| 4463 | | else |
| 4464 | | // Optional representation is bool indicating whether the optional is set |
| 4465 | | // Optionals with no payload are represented as an (indirect) bool, so convert |
| 4466 | | // it back to the direct bool here. |
| 4467 | | try self.convertToDirect(Type.bool, operand_id); |
| 4880 | const is_non_null_id = blk: { |
| 4881 | if (is_pointer) { |
| 4882 | if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { |
| 4883 | const storage_class = spvStorageClass(operand_ty.ptrAddressSpace(mod)); |
| 4884 | const bool_ptr_ty = try self.ptrType(Type.bool, storage_class); |
| 4885 | const tag_ptr_id = try self.accessChain(bool_ptr_ty, operand_id, &.{1}); |
| 4886 | break :blk try self.load(Type.bool, tag_ptr_id, .{}); |
| 4887 | } |
| 4888 | |
| 4889 | break :blk try self.load(Type.bool, operand_id, .{}); |
| 4890 | } |
| 4891 | |
| 4892 | break :blk if (payload_ty.hasRuntimeBitsIgnoreComptime(mod)) |
| 4893 | try self.extractField(Type.bool, operand_id, 1) |
| 4894 | else |
| 4895 | // Optional representation is bool indicating whether the optional is set |
| 4896 | // Optionals with no payload are represented as an (indirect) bool, so convert |
| 4897 | // it back to the direct bool here. |
| 4898 | try self.convertToDirect(Type.bool, operand_id); |
| 4899 | }; |
| 4468 | 4900 | |
| 4469 | 4901 | return switch (pred) { |
| 4470 | 4902 | .is_null => blk: { |
| ... | ... | @@ -4535,6 +4967,32 @@ const DeclGen = struct { |
| 4535 | 4967 | return try self.extractField(payload_ty, operand_id, 0); |
| 4536 | 4968 | } |
| 4537 | 4969 | |
| 4970 | fn airUnwrapOptionalPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 4971 | if (self.liveness.isUnused(inst)) return null; |
| 4972 | |
| 4973 | const mod = self.module; |
| 4974 | const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 4975 | const operand_id = try self.resolve(ty_op.operand); |
| 4976 | const operand_ty = self.typeOf(ty_op.operand); |
| 4977 | const optional_ty = operand_ty.childType(mod); |
| 4978 | const payload_ty = optional_ty.optionalChild(mod); |
| 4979 | const result_ty = self.typeOfIndex(inst); |
| 4980 | const result_ty_ref = try self.resolveType(result_ty, .direct); |
| 4981 | |
| 4982 | if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) { |
| 4983 | // There is no payload, but we still need to return a valid pointer. |
| 4984 | // We can just return anything here, so just return a pointer to the operand. |
| 4985 | return try self.bitCast(result_ty, operand_ty, operand_id); |
| 4986 | } |
| 4987 | |
| 4988 | if (optional_ty.optionalReprIsPayload(mod)) { |
| 4989 | // They are the same value. |
| 4990 | return try self.bitCast(result_ty, operand_ty, operand_id); |
| 4991 | } |
| 4992 | |
| 4993 | return try self.accessChain(result_ty_ref, operand_id, &.{0}); |
| 4994 | } |
| 4995 | |
| 4538 | 4996 | fn airWrapOptional(self: *DeclGen, inst: Air.Inst.Index) !?IdRef { |
| 4539 | 4997 | if (self.liveness.isUnused(inst)) return null; |
| 4540 | 4998 | |