authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-01-15 23:06:54+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-02-04 19:09:00+01:00
logcb9e20da00a2c33706e2c7bf2008887c6c72a896
treede3eb17f2d283a1a918089b437854b3cae65133c
parent747f4ae3f5efc89df0b1b76787eb90eab90fc362
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: element-wise operation helper


2 files changed, 97 insertions(+), 27 deletions(-)

src/codegen/spirv.zig+96-27
......@@ -1760,6 +1760,92 @@ const DeclGen = struct {
17601760 return union_layout;
17611761 }
17621762
1763 /// This structure is used as helper for element-wise operations. It is intended
1764 /// to be used with both vectors and single elements.
1765 const WipElementWise = struct {
1766 dg: *DeclGen,
1767 result_ty: Type,
1768 /// Always in direct representation.
1769 result_ty_ref: CacheRef,
1770 scalar_ty: Type,
1771 /// Always in direct representation.
1772 scalar_ty_ref: CacheRef,
1773 scalar_ty_id: IdRef,
1774 /// True if the input is actually a vector type.
1775 is_vector: bool,
1776 /// The element-wise operation should fill these results before calling finalize().
1777 /// These should all be in **direct** representation! `finalize()` will convert
1778 /// them to indirect if required.
1779 results: []IdRef,
1780
1781 fn deinit(wip: *WipElementWise) void {
1782 wip.dg.gpa.free(wip.results);
1783 }
1784
1785 /// Utility function to extract the element at a particular index in an
1786 /// input vector. This type is expected to be a vector if `wip.is_vector`, and
1787 /// a scalar otherwise.
1788 fn elementAt(wip: WipElementWise, ty: Type, value: IdRef, index: usize) !IdRef {
1789 const mod = wip.dg.module;
1790 if (wip.is_vector) {
1791 assert(ty.isVector(mod));
1792 return try wip.dg.extractField(ty, value, @intCast(index));
1793 } else {
1794 assert(!ty.isVector(mod));
1795 assert(index == 0);
1796 return value;
1797 }
1798 }
1799
1800 /// Turns the results of this WipElementWise into a result. This can either
1801 /// be a vector or single element, depending on `result_ty`.
1802 /// After calling this function, this WIP is no longer usable.
1803 /// Results is in `direct` representation.
1804 fn finalize(wip: *WipElementWise) !IdRef {
1805 if (wip.is_vector) {
1806 // Convert all the constituents to indirect, as required for the array.
1807 for (wip.results) |*result| {
1808 result.* = try wip.dg.convertToIndirect(wip.scalar_ty, result.*);
1809 }
1810 return try wip.dg.constructArray(wip.result_ty, wip.results);
1811 } else {
1812 return wip.results[0];
1813 }
1814 }
1815
1816 /// Allocate a result id at a particular index, and return it.
1817 fn allocId(wip: *WipElementWise, index: usize) IdRef {
1818 assert(wip.is_vector or index == 0);
1819 wip.results[index] = wip.dg.spv.allocId();
1820 return wip.results[index];
1821 }
1822 };
1823
1824 /// Create a new element-wise operation.
1825 fn elementWise(self: *DeclGen, result_ty: Type) !WipElementWise {
1826 const mod = self.module;
1827 // For now, this operation also reasons in terms of `.direct` representation.
1828 const result_ty_ref = try self.resolveType(result_ty, .direct);
1829 const is_vector = result_ty.isVector(mod);
1830 const num_results = if (is_vector) result_ty.vectorLen(mod) else 1;
1831 const results = try self.gpa.alloc(IdRef, num_results);
1832 for (results) |*result| result.* = undefined;
1833
1834 const scalar_ty = if (is_vector) result_ty.childType(mod) else result_ty;
1835 const scalar_ty_ref = try self.resolveType(scalar_ty, .direct);
1836
1837 return .{
1838 .dg = self,
1839 .result_ty = result_ty,
1840 .result_ty_ref = result_ty_ref,
1841 .scalar_ty = scalar_ty,
1842 .scalar_ty_ref = scalar_ty_ref,
1843 .scalar_ty_id = self.typeId(scalar_ty_ref),
1844 .is_vector = is_vector,
1845 .results = results,
1846 };
1847 }
1848
17631849 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
17641850 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
17651851 /// points. The test executor will then be able to invoke these to run the tests.
......@@ -2214,34 +2300,17 @@ const DeclGen = struct {
22142300 }
22152301
22162302 fn binOpSimple(self: *DeclGen, ty: Type, lhs_id: IdRef, rhs_id: IdRef, comptime opcode: Opcode) !IdRef {
2217 const mod = self.module;
2218
2219 if (ty.isVector(mod)) {
2220 const child_ty = ty.childType(mod);
2221 const vector_len = ty.vectorLen(mod);
2222
2223 const constituents = try self.gpa.alloc(IdRef, vector_len);
2224 defer self.gpa.free(constituents);
2225
2226 for (constituents, 0..) |*constituent, i| {
2227 const lhs_index_id = try self.extractField(child_ty, lhs_id, @intCast(i));
2228 const rhs_index_id = try self.extractField(child_ty, rhs_id, @intCast(i));
2229 const result_id = try self.binOpSimple(child_ty, lhs_index_id, rhs_index_id, opcode);
2230 constituent.* = try self.convertToIndirect(child_ty, result_id);
2231 }
2232
2233 return try self.constructArray(ty, constituents);
2303 var wip = try self.elementWise(ty);
2304 defer wip.deinit();
2305 for (0..wip.results.len) |i| {
2306 try self.func.body.emit(self.spv.gpa, opcode, .{
2307 .id_result_type = wip.scalar_ty_id,
2308 .id_result = wip.allocId(i),
2309 .operand_1 = try wip.elementAt(ty, lhs_id, i),
2310 .operand_2 = try wip.elementAt(ty, rhs_id, i),
2311 });
22342312 }
2235
2236 const result_id = self.spv.allocId();
2237 const result_type_id = try self.resolveTypeId(ty);
2238 try self.func.body.emit(self.spv.gpa, opcode, .{
2239 .id_result_type = result_type_id,
2240 .id_result = result_id,
2241 .operand_1 = lhs_id,
2242 .operand_2 = rhs_id,
2243 });
2244 return result_id;
2313 return try wip.finalize();
22452314 }
22462315
22472316 fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, comptime opcode: Opcode) !?IdRef {
test/behavior/math.zig+1
......@@ -12,6 +12,7 @@ const math = std.math;
1212test "assignment operators" {
1313 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1414 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
15 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
1516
1617 var i: u32 = 0;
1718 i += 5;