authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-11-09 21:50:33+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-11-09 21:50:33+01:00
log62f4a6b4d877ce03d1e6f59cf794b5ebc6ea41d0
treef86e516ad8fc2b14b43c57a48ba3a3db3a10e41a
parent35201e9d9338537a92de2ff89ea23dcd22ce4e52
parent9cd7b8359c435a7a0c1309cbf529a100d5422b4f
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #21937 from Snektron/spirv-vulkan-ptrs

spirv: miscellaneous vulkan + zig stuff

8 files changed, 231 insertions(+), 57 deletions(-)

lib/std/Target.zig+1-1
...@@ -1573,7 +1573,7 @@ pub const Cpu = struct {...@@ -1573,7 +1573,7 @@ pub const Cpu = struct {
1573 .fs, .gs, .ss => arch == .x86_64 or arch == .x86,1573 .fs, .gs, .ss => arch == .x86_64 or arch == .x86,
1574 .global, .constant, .local, .shared => is_gpu,1574 .global, .constant, .local, .shared => is_gpu,
1575 .param => is_nvptx,1575 .param => is_nvptx,
1576 .input, .output, .uniform, .push_constant => is_spirv,1576 .input, .output, .uniform, .push_constant, .storage_buffer => is_spirv,
1577 // TODO this should also check how many flash banks the cpu has1577 // TODO this should also check how many flash banks the cpu has
1578 .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => arch == .avr,1578 .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => arch == .avr,
15791579
lib/std/builtin.zig+1
...@@ -515,6 +515,7 @@ pub const AddressSpace = enum(u5) {...@@ -515,6 +515,7 @@ pub const AddressSpace = enum(u5) {
515 output,515 output,
516 uniform,516 uniform,
517 push_constant,517 push_constant,
518 storage_buffer,
518519
519 // AVR address spaces.520 // AVR address spaces.
520 flash,521 flash,
src/Sema.zig+20-1
...@@ -17606,6 +17606,25 @@ fn analyzePtrArithmetic(...@@ -17606,6 +17606,25 @@ fn analyzePtrArithmetic(
17606 };17606 };
1760717607
17608 try sema.requireRuntimeBlock(block, op_src, runtime_src);17608 try sema.requireRuntimeBlock(block, op_src, runtime_src);
17609
17610 const target = zcu.getTarget();
17611 if (target_util.arePointersLogical(target, ptr_info.flags.address_space)) {
17612 return sema.failWithOwnedErrorMsg(block, msg: {
17613 const msg = try sema.errMsg(op_src, "illegal pointer arithmetic on pointer of type '{}'", .{ptr_ty.fmt(pt)});
17614 errdefer msg.destroy(sema.gpa);
17615
17616 const backend = target_util.zigBackend(target, zcu.comp.config.use_llvm);
17617 try sema.errNote(op_src, msg, "arithmetic cannot be performed on pointers with address space '{s}' on target {s}-{s} by compiler backend {s}", .{
17618 @tagName(ptr_info.flags.address_space),
17619 target.cpu.arch.genericName(),
17620 @tagName(target.os.tag),
17621 @tagName(backend),
17622 });
17623
17624 break :msg msg;
17625 });
17626 }
17627
17609 return block.addInst(.{17628 return block.addInst(.{
17610 .tag = air_tag,17629 .tag = air_tag,
17611 .data = .{ .ty_pl = .{17630 .data = .{ .ty_pl = .{
...@@ -37833,7 +37852,7 @@ pub fn analyzeAsAddressSpace(...@@ -37833,7 +37852,7 @@ pub fn analyzeAsAddressSpace(
37833 .gs, .fs, .ss => (arch == .x86 or arch == .x86_64) and ctx == .pointer,37852 .gs, .fs, .ss => (arch == .x86 or arch == .x86_64) and ctx == .pointer,
37834 // TODO: check that .shared and .local are left uninitialized37853 // TODO: check that .shared and .local are left uninitialized
37835 .param => is_nv,37854 .param => is_nv,
37836 .input, .output, .uniform, .push_constant => is_spirv,37855 .input, .output, .uniform, .push_constant, .storage_buffer => is_spirv,
37837 .global, .shared, .local => is_gpu,37856 .global, .shared, .local => is_gpu,
37838 .constant => is_gpu and (ctx == .constant),37857 .constant => is_gpu and (ctx == .constant),
37839 // TODO this should also check how many flash banks the cpu has37858 // TODO this should also check how many flash banks the cpu has
src/codegen/spirv.zig+108-39
...@@ -1396,10 +1396,6 @@ const NavGen = struct {...@@ -1396,10 +1396,6 @@ const NavGen = struct {
13961396
1397 const child_ty_id = try self.resolveType(child_ty, child_repr);1397 const child_ty_id = try self.resolveType(child_ty, child_repr);
13981398
1399 if (storage_class == .Uniform or storage_class == .PushConstant) {
1400 try self.spv.decorate(child_ty_id, .Block);
1401 }
1402
1403 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{1399 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
1404 .id_result = result_id,1400 .id_result = result_id,
1405 .storage_class = storage_class,1401 .storage_class = storage_class,
...@@ -1643,7 +1639,11 @@ const NavGen = struct {...@@ -1643,7 +1639,11 @@ const NavGen = struct {
1643 return try self.arrayType(1, elem_ty_id);1639 return try self.arrayType(1, elem_ty_id);
1644 } else {1640 } else {
1645 const result_id = try self.arrayType(total_len, elem_ty_id);1641 const result_id = try self.arrayType(total_len, elem_ty_id);
1646 try self.spv.decorate(result_id, .{ .ArrayStride = .{ .array_stride = @intCast(elem_ty.abiSize(zcu)) } });1642 if (target.os.tag == .vulkan) {
1643 try self.spv.decorate(result_id, .{ .ArrayStride = .{
1644 .array_stride = @intCast(elem_ty.abiSize(zcu)),
1645 } });
1646 }
1647 return result_id;1647 return result_id;
1648 }1648 }
1649 },1649 },
...@@ -1662,7 +1662,7 @@ const NavGen = struct {...@@ -1662,7 +1662,7 @@ const NavGen = struct {
1662 else => unreachable,1662 else => unreachable,
1663 }1663 }
16641664
1665 // Guaranteed by callConvSupportsVarArgs, there are nog SPIR-V CCs which support1665 // Guaranteed by callConvSupportsVarArgs, there are no SPIR-V CCs which support
1666 // varargs.1666 // varargs.
1667 assert(!fn_info.is_var_args);1667 assert(!fn_info.is_var_args);
16681668
...@@ -1698,8 +1698,15 @@ const NavGen = struct {...@@ -1698,8 +1698,15 @@ const NavGen = struct {
1698 .pointer => {1698 .pointer => {
1699 const ptr_info = ty.ptrInfo(zcu);1699 const ptr_info = ty.ptrInfo(zcu);
17001700
1701 const child_ty = Type.fromInterned(ptr_info.child);
1701 const storage_class = self.spvStorageClass(ptr_info.flags.address_space);1702 const storage_class = self.spvStorageClass(ptr_info.flags.address_space);
1702 const ptr_ty_id = try self.ptrType(Type.fromInterned(ptr_info.child), storage_class);1703 const ptr_ty_id = try self.ptrType(child_ty, storage_class);
1704
1705 if (target.os.tag == .vulkan and ptr_info.flags.size == .Many) {
1706 try self.spv.decorate(ptr_ty_id, .{ .ArrayStride = .{
1707 .array_stride = @intCast(child_ty.abiSize(zcu)),
1708 } });
1709 }
17031710
1704 if (ptr_info.flags.size != .Slice) {1711 if (ptr_info.flags.size != .Slice) {
1705 return ptr_ty_id;1712 return ptr_ty_id;
...@@ -1746,6 +1753,10 @@ const NavGen = struct {...@@ -1746,6 +1753,10 @@ const NavGen = struct {
1746 defer self.gpa.free(type_name);1753 defer self.gpa.free(type_name);
1747 try self.spv.debugName(result_id, type_name);1754 try self.spv.debugName(result_id, type_name);
17481755
1756 if (target.os.tag == .vulkan) {
1757 try self.spv.decorate(result_id, .Block); // Decorate all structs as block for now...
1758 }
1759
1749 return result_id;1760 return result_id;
1750 },1761 },
1751 .struct_type => ip.loadStructType(ty.toIntern()),1762 .struct_type => ip.loadStructType(ty.toIntern()),
...@@ -1791,6 +1802,10 @@ const NavGen = struct {...@@ -1791,6 +1802,10 @@ const NavGen = struct {
1791 defer self.gpa.free(type_name);1802 defer self.gpa.free(type_name);
1792 try self.spv.debugName(result_id, type_name);1803 try self.spv.debugName(result_id, type_name);
17931804
1805 if (target.os.tag == .vulkan) {
1806 try self.spv.decorate(result_id, .Block); // Decorate all structs as block for now...
1807 }
1808
1794 return result_id;1809 return result_id;
1795 },1810 },
1796 .optional => {1811 .optional => {
...@@ -1882,7 +1897,7 @@ const NavGen = struct {...@@ -1882,7 +1897,7 @@ const NavGen = struct {
1882 else => unreachable,1897 else => unreachable,
1883 },1898 },
1884 .shared => .Workgroup,1899 .shared => .Workgroup,
1885 .local => .Private,1900 .local => .Function,
1886 .global => switch (target.os.tag) {1901 .global => switch (target.os.tag) {
1887 .opencl => .CrossWorkgroup,1902 .opencl => .CrossWorkgroup,
1888 .vulkan => .PhysicalStorageBuffer,1903 .vulkan => .PhysicalStorageBuffer,
...@@ -1893,6 +1908,7 @@ const NavGen = struct {...@@ -1893,6 +1908,7 @@ const NavGen = struct {
1893 .input => .Input,1908 .input => .Input,
1894 .output => .Output,1909 .output => .Output,
1895 .uniform => .Uniform,1910 .uniform => .Uniform,
1911 .storage_buffer => .StorageBuffer,
1896 .gs,1912 .gs,
1897 .fs,1913 .fs,
1898 .ss,1914 .ss,
...@@ -4354,13 +4370,24 @@ const NavGen = struct {...@@ -4354,13 +4370,24 @@ const NavGen = struct {
4354 defer self.gpa.free(ids);4370 defer self.gpa.free(ids);
43554371
4356 const result_id = self.spv.allocId();4372 const result_id = self.spv.allocId();
4357 try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{4373 const target = self.getTarget();
4358 .id_result_type = result_ty_id,4374 switch (target.os.tag) {
4359 .id_result = result_id,4375 .opencl => try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{
4360 .base = base,4376 .id_result_type = result_ty_id,
4361 .element = element,4377 .id_result = result_id,
4362 .indexes = ids,4378 .base = base,
4363 });4379 .element = element,
4380 .indexes = ids,
4381 }),
4382 .vulkan => try self.func.body.emit(self.spv.gpa, .OpPtrAccessChain, .{
4383 .id_result_type = result_ty_id,
4384 .id_result = result_id,
4385 .base = base,
4386 .element = element,
4387 .indexes = ids,
4388 }),
4389 else => unreachable,
4390 }
4364 return result_id;4391 return result_id;
4365 }4392 }
43664393
...@@ -6529,6 +6556,13 @@ const NavGen = struct {...@@ -6529,6 +6556,13 @@ const NavGen = struct {
6529 return self.todo("implement inline asm with more than 1 output", .{});6556 return self.todo("implement inline asm with more than 1 output", .{});
6530 }6557 }
65316558
6559 var as = SpvAssembler{
6560 .gpa = self.gpa,
6561 .spv = self.spv,
6562 .func = &self.func,
6563 };
6564 defer as.deinit();
6565
6532 var output_extra_i = extra_i;6566 var output_extra_i = extra_i;
6533 for (outputs) |output| {6567 for (outputs) |output| {
6534 if (output != .none) {6568 if (output != .none) {
...@@ -6541,7 +6575,6 @@ const NavGen = struct {...@@ -6541,7 +6575,6 @@ const NavGen = struct {
6541 // TODO: Record output and use it somewhere.6575 // TODO: Record output and use it somewhere.
6542 }6576 }
65436577
6544 var input_extra_i = extra_i;
6545 for (inputs) |input| {6578 for (inputs) |input| {
6546 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);6579 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
6547 const constraint = std.mem.sliceTo(extra_bytes, 0);6580 const constraint = std.mem.sliceTo(extra_bytes, 0);
...@@ -6549,8 +6582,63 @@ const NavGen = struct {...@@ -6549,8 +6582,63 @@ const NavGen = struct {
6549 // This equation accounts for the fact that even if we have exactly 4 bytes6582 // This equation accounts for the fact that even if we have exactly 4 bytes
6550 // for the string, we still use the next u32 for the null terminator.6583 // for the string, we still use the next u32 for the null terminator.
6551 extra_i += (constraint.len + name.len + (2 + 3)) / 4;6584 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
6552 // TODO: Record input and use it somewhere.6585
6553 _ = input;6586 const input_ty = self.typeOf(input);
6587
6588 if (std.mem.eql(u8, constraint, "c")) {
6589 // constant
6590 const val = (try self.air.value(input, self.pt)) orelse {
6591 return self.fail("assembly inputs with 'c' constraint have to be compile-time known", .{});
6592 };
6593
6594 // TODO: This entire function should be handled a bit better...
6595 const ip = &zcu.intern_pool;
6596 switch (ip.indexToKey(val.toIntern())) {
6597 .int_type,
6598 .ptr_type,
6599 .array_type,
6600 .vector_type,
6601 .opt_type,
6602 .anyframe_type,
6603 .error_union_type,
6604 .simple_type,
6605 .struct_type,
6606 .union_type,
6607 .opaque_type,
6608 .enum_type,
6609 .func_type,
6610 .error_set_type,
6611 .inferred_error_set_type,
6612 => unreachable, // types, not values
6613
6614 .undef => return self.fail("assembly input with 'c' constraint cannot be undefined", .{}),
6615
6616 .int => {
6617 try as.value_map.put(as.gpa, name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) });
6618 },
6619
6620 else => unreachable, // TODO
6621 }
6622 } else if (std.mem.eql(u8, constraint, "t")) {
6623 // type
6624 if (input_ty.zigTypeTag(zcu) == .type) {
6625 // This assembly input is a type instead of a value.
6626 // That's fine for now, just make sure to resolve it as such.
6627 const val = (try self.air.value(input, self.pt)).?;
6628 const ty_id = try self.resolveType(val.toType(), .direct);
6629 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });
6630 } else {
6631 const ty_id = try self.resolveType(input_ty, .direct);
6632 try as.value_map.put(as.gpa, name, .{ .ty = ty_id });
6633 }
6634 } else {
6635 if (input_ty.zigTypeTag(zcu) == .type) {
6636 return self.fail("use the 't' constraint to supply types to SPIR-V inline assembly", .{});
6637 }
6638
6639 const val_id = try self.resolve(input);
6640 try as.value_map.put(as.gpa, name, .{ .value = val_id });
6641 }
6554 }6642 }
65556643
6556 {6644 {
...@@ -6564,27 +6652,7 @@ const NavGen = struct {...@@ -6564,27 +6652,7 @@ const NavGen = struct {
65646652
6565 const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len];6653 const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len];
65666654
6567 var as = SpvAssembler{6655 as.assemble(asm_source) catch |err| switch (err) {
6568 .gpa = self.gpa,
6569 .src = asm_source,
6570 .spv = self.spv,
6571 .func = &self.func,
6572 };
6573 defer as.deinit();
6574
6575 for (inputs) |input| {
6576 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[input_extra_i..]);
6577 const constraint = std.mem.sliceTo(extra_bytes, 0);
6578 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
6579 // This equation accounts for the fact that even if we have exactly 4 bytes
6580 // for the string, we still use the next u32 for the null terminator.
6581 input_extra_i += (constraint.len + name.len + (2 + 3)) / 4;
6582
6583 const value = try self.resolve(input);
6584 try as.value_map.put(as.gpa, name, .{ .value = value });
6585 }
6586
6587 as.assemble() catch |err| switch (err) {
6588 error.AssembleFail => {6656 error.AssembleFail => {
6589 // TODO: For now the compiler only supports a single error message per decl,6657 // TODO: For now the compiler only supports a single error message per decl,
6590 // so to translate the possible multiple errors from the assembler, emit6658 // so to translate the possible multiple errors from the assembler, emit
...@@ -6629,6 +6697,7 @@ const NavGen = struct {...@@ -6629,6 +6697,7 @@ const NavGen = struct {
6629 .just_declared, .unresolved_forward_reference => unreachable,6697 .just_declared, .unresolved_forward_reference => unreachable,
6630 .ty => return self.fail("cannot return spir-v type as value from assembly", .{}),6698 .ty => return self.fail("cannot return spir-v type as value from assembly", .{}),
6631 .value => |ref| return ref,6699 .value => |ref| return ref,
6700 .constant => return self.fail("cannot return constant from assembly", .{}),
6632 }6701 }
66336702
6634 // TODO: Multiple results6703 // TODO: Multiple results
src/codegen/spirv/Assembler.zig+91-14
...@@ -45,6 +45,9 @@ const Token = struct {...@@ -45,6 +45,9 @@ const Token = struct {
45 pipe,45 pipe,
46 /// =.46 /// =.
47 equals,47 equals,
48 /// $identifier. This is used (for now) for constant values, like integers.
49 /// These can be used in place of a normal `value`.
50 placeholder,
4851
49 fn name(self: Tag) []const u8 {52 fn name(self: Tag) []const u8 {
50 return switch (self) {53 return switch (self) {
...@@ -56,6 +59,7 @@ const Token = struct {...@@ -56,6 +59,7 @@ const Token = struct {
56 .string => "<string literal>",59 .string => "<string literal>",
57 .pipe => "'|'",60 .pipe => "'|'",
58 .equals => "'='",61 .equals => "'='",
62 .placeholder => "<placeholder>",
59 };63 };
60 }64 }
61 };65 };
...@@ -128,12 +132,19 @@ const AsmValue = union(enum) {...@@ -128,12 +132,19 @@ const AsmValue = union(enum) {
128 /// This result-value represents a type registered into the module's type system.132 /// This result-value represents a type registered into the module's type system.
129 ty: IdRef,133 ty: IdRef,
130134
135 /// This is a pre-supplied constant integer value.
136 constant: u32,
137
131 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue138 /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue
132 /// is of a variant that allows the result to be obtained (not an unresolved139 /// is of a variant that allows the result to be obtained (not an unresolved
133 /// forward declaration, not in the process of being declared, etc).140 /// forward declaration, not in the process of being declared, etc).
134 pub fn resultId(self: AsmValue) IdRef {141 pub fn resultId(self: AsmValue) IdRef {
135 return switch (self) {142 return switch (self) {
136 .just_declared, .unresolved_forward_reference => unreachable,143 .just_declared,
144 .unresolved_forward_reference,
145 // TODO: Lower this value as constant?
146 .constant,
147 => unreachable,
137 .value => |result| result,148 .value => |result| result,
138 .ty => |result| result,149 .ty => |result| result,
139 };150 };
...@@ -151,7 +162,8 @@ gpa: Allocator,...@@ -151,7 +162,8 @@ gpa: Allocator,
151errors: std.ArrayListUnmanaged(ErrorMsg) = .empty,162errors: std.ArrayListUnmanaged(ErrorMsg) = .empty,
152163
153/// The source code that is being assembled.164/// The source code that is being assembled.
154src: []const u8,165/// This is set when calling `assemble()`.
166src: []const u8 = undefined,
155167
156/// The module that this assembly is associated to.168/// The module that this assembly is associated to.
157/// Instructions like OpType*, OpDecorate, etc are emitted into this module.169/// Instructions like OpType*, OpDecorate, etc are emitted into this module.
...@@ -211,7 +223,10 @@ pub fn deinit(self: *Assembler) void {...@@ -211,7 +223,10 @@ pub fn deinit(self: *Assembler) void {
211 self.instruction_map.deinit(self.gpa);223 self.instruction_map.deinit(self.gpa);
212}224}
213225
214pub fn assemble(self: *Assembler) Error!void {226pub fn assemble(self: *Assembler, src: []const u8) Error!void {
227 self.src = src;
228 self.errors.clearRetainingCapacity();
229
215 // Populate the opcode map if it isn't already230 // Populate the opcode map if it isn't already
216 if (self.instruction_map.count() == 0) {231 if (self.instruction_map.count() == 0) {
217 const instructions = spec.InstructionSet.core.instructions();232 const instructions = spec.InstructionSet.core.instructions();
...@@ -369,6 +384,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {...@@ -369,6 +384,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue {
369/// - Function-local instructions are emitted in `self.func`.384/// - Function-local instructions are emitted in `self.func`.
370fn processGenericInstruction(self: *Assembler) !?AsmValue {385fn processGenericInstruction(self: *Assembler) !?AsmValue {
371 const operands = self.inst.operands.items;386 const operands = self.inst.operands.items;
387 var maybe_spv_decl_index: ?SpvModule.Decl.Index = null;
372 const section = switch (self.inst.opcode.class()) {388 const section = switch (self.inst.opcode.class()) {
373 .ConstantCreation => &self.spv.sections.types_globals_constants,389 .ConstantCreation => &self.spv.sections.types_globals_constants,
374 .Annotation => &self.spv.sections.annotations,390 .Annotation => &self.spv.sections.annotations,
...@@ -378,13 +394,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {...@@ -378,13 +394,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
378 .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,394 .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes,
379 .OpVariable => switch (@as(spec.StorageClass, @enumFromInt(operands[2].value))) {395 .OpVariable => switch (@as(spec.StorageClass, @enumFromInt(operands[2].value))) {
380 .Function => &self.func.prologue,396 .Function => &self.func.prologue,
381 .UniformConstant => &self.spv.sections.types_globals_constants,397 .Input, .Output => section: {
382 else => {398 maybe_spv_decl_index = try self.spv.allocDecl(.global);
383 // This is currently disabled because global variables are required to be399 try self.func.decl_deps.put(self.spv.gpa, maybe_spv_decl_index.?, {});
384 // emitted in the proper order, and this should be honored in inline assembly400 // TODO: In theory this can be non-empty if there is an initializer which depends on another global...
385 // as well.401 try self.spv.declareDeclDeps(maybe_spv_decl_index.?, &.{});
386 return self.todo("global variables", .{});402 break :section &self.spv.sections.types_globals_constants;
387 },403 },
404 // These don't need to be marked in the dependency system.
405 // Probably we should add them anyway, then filter out PushConstant globals.
406 else => &self.spv.sections.types_globals_constants,
388 },407 },
389 // Default case - to be worked out further.408 // Default case - to be worked out further.
390 else => &self.func.body,409 else => &self.func.body,
...@@ -409,7 +428,10 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {...@@ -409,7 +428,10 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue {
409 section.writeDoubleWord(dword);428 section.writeDoubleWord(dword);
410 },429 },
411 .result_id => {430 .result_id => {
412 maybe_result_id = self.spv.allocId();431 maybe_result_id = if (maybe_spv_decl_index) |spv_decl_index|
432 self.spv.declPtr(spv_decl_index).result_id
433 else
434 self.spv.allocId();
413 try section.ensureUnusedCapacity(self.spv.gpa, 1);435 try section.ensureUnusedCapacity(self.spv.gpa, 1);
414 section.writeOperand(IdResult, maybe_result_id.?);436 section.writeOperand(IdResult, maybe_result_id.?);
415 },437 },
...@@ -475,8 +497,8 @@ fn resolveRefId(self: *Assembler, ref: AsmValue.Ref) !IdRef {...@@ -475,8 +497,8 @@ fn resolveRefId(self: *Assembler, ref: AsmValue.Ref) !IdRef {
475/// error message has been emitted into `self.errors`.497/// error message has been emitted into `self.errors`.
476fn parseInstruction(self: *Assembler) !void {498fn parseInstruction(self: *Assembler) !void {
477 self.inst.opcode = undefined;499 self.inst.opcode = undefined;
478 self.inst.operands.shrinkRetainingCapacity(0);500 self.inst.operands.clearRetainingCapacity();
479 self.inst.string_bytes.shrinkRetainingCapacity(0);501 self.inst.string_bytes.clearRetainingCapacity();
480502
481 const lhs_result_tok = self.currentToken();503 const lhs_result_tok = self.currentToken();
482 const maybe_lhs_result: ?AsmValue.Ref = if (self.eatToken(.result_id_assign)) blk: {504 const maybe_lhs_result: ?AsmValue.Ref = if (self.eatToken(.result_id_assign)) blk: {
...@@ -654,6 +676,22 @@ fn parseRefId(self: *Assembler) !void {...@@ -654,6 +676,22 @@ fn parseRefId(self: *Assembler) !void {
654676
655fn parseLiteralInteger(self: *Assembler) !void {677fn parseLiteralInteger(self: *Assembler) !void {
656 const tok = self.currentToken();678 const tok = self.currentToken();
679 if (self.eatToken(.placeholder)) {
680 const name = self.tokenText(tok)[1..];
681 const value = self.value_map.get(name) orelse {
682 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
683 };
684 switch (value) {
685 .constant => |literal32| {
686 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
687 },
688 else => {
689 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
690 },
691 }
692 return;
693 }
694
657 try self.expectToken(.value);695 try self.expectToken(.value);
658 // According to the SPIR-V machine readable grammar, a LiteralInteger696 // According to the SPIR-V machine readable grammar, a LiteralInteger
659 // may consist of one or more words. From the SPIR-V docs it seems like there697 // may consist of one or more words. From the SPIR-V docs it seems like there
...@@ -669,6 +707,22 @@ fn parseLiteralInteger(self: *Assembler) !void {...@@ -669,6 +707,22 @@ fn parseLiteralInteger(self: *Assembler) !void {
669707
670fn parseLiteralExtInstInteger(self: *Assembler) !void {708fn parseLiteralExtInstInteger(self: *Assembler) !void {
671 const tok = self.currentToken();709 const tok = self.currentToken();
710 if (self.eatToken(.placeholder)) {
711 const name = self.tokenText(tok)[1..];
712 const value = self.value_map.get(name) orelse {
713 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
714 };
715 switch (value) {
716 .constant => |literal32| {
717 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
718 },
719 else => {
720 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
721 },
722 }
723 return;
724 }
725
672 try self.expectToken(.value);726 try self.expectToken(.value);
673 const text = self.tokenText(tok);727 const text = self.tokenText(tok);
674 const value = std.fmt.parseInt(u32, text, 0) catch {728 const value = std.fmt.parseInt(u32, text, 0) catch {
...@@ -745,6 +799,22 @@ fn parseContextDependentNumber(self: *Assembler) !void {...@@ -745,6 +799,22 @@ fn parseContextDependentNumber(self: *Assembler) !void {
745799
746fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {800fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void {
747 const tok = self.currentToken();801 const tok = self.currentToken();
802 if (self.eatToken(.placeholder)) {
803 const name = self.tokenText(tok)[1..];
804 const value = self.value_map.get(name) orelse {
805 return self.fail(tok.start, "invalid placeholder '${s}'", .{name});
806 };
807 switch (value) {
808 .constant => |literal32| {
809 try self.inst.operands.append(self.gpa, .{ .literal32 = literal32 });
810 },
811 else => {
812 return self.fail(tok.start, "value '{s}' cannot be used as placeholder", .{name});
813 },
814 }
815 return;
816 }
817
748 try self.expectToken(.value);818 try self.expectToken(.value);
749819
750 if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) {820 if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) {
...@@ -848,6 +918,8 @@ fn tokenText(self: Assembler, tok: Token) []const u8 {...@@ -848,6 +918,8 @@ fn tokenText(self: Assembler, tok: Token) []const u8 {
848/// Tokenize `self.src` and put the tokens in `self.tokens`.918/// Tokenize `self.src` and put the tokens in `self.tokens`.
849/// Any errors encountered are appended to `self.errors`.919/// Any errors encountered are appended to `self.errors`.
850fn tokenize(self: *Assembler) !void {920fn tokenize(self: *Assembler) !void {
921 self.tokens.clearRetainingCapacity();
922
851 var offset: u32 = 0;923 var offset: u32 = 0;
852 while (true) {924 while (true) {
853 const tok = try self.nextToken(offset);925 const tok = try self.nextToken(offset);
...@@ -890,6 +962,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {...@@ -890,6 +962,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
890 string,962 string,
891 string_end,963 string_end,
892 escape,964 escape,
965 placeholder,
893 } = .start;966 } = .start;
894 var token_start = start_offset;967 var token_start = start_offset;
895 var offset = start_offset;968 var offset = start_offset;
...@@ -917,6 +990,10 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {...@@ -917,6 +990,10 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
917 offset += 1;990 offset += 1;
918 break;991 break;
919 },992 },
993 '$' => {
994 state = .placeholder;
995 tag = .placeholder;
996 },
920 else => {997 else => {
921 state = .value;998 state = .value;
922 tag = .value;999 tag = .value;
...@@ -932,11 +1009,11 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {...@@ -932,11 +1009,11 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token {
932 ' ', '\t', '\r', '\n', '=', '|' => break,1009 ' ', '\t', '\r', '\n', '=', '|' => break,
933 else => {},1010 else => {},
934 },1011 },
935 .result_id => switch (c) {1012 .result_id, .placeholder => switch (c) {
936 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},1013 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},
937 ' ', '\t', '\r', '\n', '=', '|' => break,1014 ' ', '\t', '\r', '\n', '=', '|' => break,
938 else => {1015 else => {
939 try self.addError(offset, "illegal character in result-id", .{});1016 try self.addError(offset, "illegal character in result-id or placeholder", .{});
940 // Again, probably a forgotten delimiter here.1017 // Again, probably a forgotten delimiter here.
941 break;1018 break;
942 },1019 },
src/link/SpirV.zig+1-1
...@@ -296,7 +296,7 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {...@@ -296,7 +296,7 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
296 // TODO: Integrate with a hypothetical feature system296 // TODO: Integrate with a hypothetical feature system
297 const caps: []const spec.Capability = switch (target.os.tag) {297 const caps: []const spec.Capability = switch (target.os.tag) {
298 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .Vector16, .GenericPointer },298 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .Vector16, .GenericPointer },
299 .vulkan => &.{ .Shader, .PhysicalStorageBufferAddresses, .Int8, .Int16, .Int64, .Float64, .Float16 },299 .vulkan => &.{ .Shader, .PhysicalStorageBufferAddresses, .Int8, .Int16, .Int64, .Float64, .Float16, .VariablePointers, .VariablePointersStorageBuffer },
300 else => unreachable,300 else => unreachable,
301 };301 };
302302
src/link/SpirV/deduplicate.zig+8
...@@ -511,6 +511,14 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr...@@ -511,6 +511,14 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr
511 }511 }
512512
513 if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {513 if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {
514 // Only emit forward pointers before type, constant, and global instructions.
515 // Debug and Annotation instructions don't need the forward pointer, and it
516 // messes up the logical layout of the module.
517 switch (inst.opcode.class()) {
518 .TypeDeclaration, .ConstantCreation, .Memory => {},
519 else => continue,
520 }
521
514 const id: ResultId = @enumFromInt(operand.*);522 const id: ResultId = @enumFromInt(operand.*);
515 const index = info.entities.getIndex(id) orelse continue;523 const index = info.entities.getIndex(id) orelse continue;
516 const entity = info.entities.values()[index];524 const entity = info.entities.values()[index];
src/target.zig+1-1
...@@ -458,7 +458,7 @@ pub fn arePointersLogical(target: std.Target, as: AddressSpace) bool {...@@ -458,7 +458,7 @@ pub fn arePointersLogical(target: std.Target, as: AddressSpace) bool {
458 .global => false,458 .global => false,
459 // TODO: Allowed with VK_KHR_variable_pointers.459 // TODO: Allowed with VK_KHR_variable_pointers.
460 .shared => true,460 .shared => true,
461 .constant, .local, .input, .output, .uniform, .push_constant => true,461 .constant, .local, .input, .output, .uniform, .push_constant, .storage_buffer => true,
462 else => unreachable,462 else => unreachable,
463 };463 };
464}464}