| author | |
| committer | |
| log | 62f4a6b4d877ce03d1e6f59cf794b5ebc6ea41d0 |
| tree | f86e516ad8fc2b14b43c57a48ba3a3db3a10e41a |
| parent | 35201e9d9338537a92de2ff89ea23dcd22ce4e52 |
| parent | 9cd7b8359c435a7a0c1309cbf529a100d5422b4f |
| signature |
spirv: miscellaneous vulkan + zig stuff8 files changed, 231 insertions(+), 57 deletions(-)
lib/std/Target.zig+1-1| ... | ... | @@ -1573,7 +1573,7 @@ pub const Cpu = struct { |
| 1573 | 1573 | .fs, .gs, .ss => arch == .x86_64 or arch == .x86, |
| 1574 | 1574 | .global, .constant, .local, .shared => is_gpu, |
| 1575 | 1575 | .param => is_nvptx, |
| 1576 | .input, .output, .uniform, .push_constant => is_spirv, | |
| 1576 | .input, .output, .uniform, .push_constant, .storage_buffer => is_spirv, | |
| 1577 | 1577 | // TODO this should also check how many flash banks the cpu has |
| 1578 | 1578 | .flash, .flash1, .flash2, .flash3, .flash4, .flash5 => arch == .avr, |
| 1579 | 1579 |
lib/std/builtin.zig+1| ... | ... | @@ -515,6 +515,7 @@ pub const AddressSpace = enum(u5) { |
| 515 | 515 | output, |
| 516 | 516 | uniform, |
| 517 | 517 | push_constant, |
| 518 | storage_buffer, | |
| 518 | 519 | |
| 519 | 520 | // AVR address spaces. |
| 520 | 521 | flash, |
src/Sema.zig+20-1| ... | ... | @@ -17606,6 +17606,25 @@ fn analyzePtrArithmetic( |
| 17606 | 17606 | }; |
| 17607 | 17607 | |
| 17608 | 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 | 17628 | return block.addInst(.{ |
| 17610 | 17629 | .tag = air_tag, |
| 17611 | 17630 | .data = .{ .ty_pl = .{ |
| ... | ... | @@ -37833,7 +37852,7 @@ pub fn analyzeAsAddressSpace( |
| 37833 | 37852 | .gs, .fs, .ss => (arch == .x86 or arch == .x86_64) and ctx == .pointer, |
| 37834 | 37853 | // TODO: check that .shared and .local are left uninitialized |
| 37835 | 37854 | .param => is_nv, |
| 37836 | .input, .output, .uniform, .push_constant => is_spirv, | |
| 37855 | .input, .output, .uniform, .push_constant, .storage_buffer => is_spirv, | |
| 37837 | 37856 | .global, .shared, .local => is_gpu, |
| 37838 | 37857 | .constant => is_gpu and (ctx == .constant), |
| 37839 | 37858 | // 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 | 1396 | |
| 1397 | 1397 | const child_ty_id = try self.resolveType(child_ty, child_repr); |
| 1398 | 1398 | |
| 1399 | if (storage_class == .Uniform or storage_class == .PushConstant) { | |
| 1400 | try self.spv.decorate(child_ty_id, .Block); | |
| 1401 | } | |
| 1402 | ||
| 1403 | 1399 | try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{ |
| 1404 | 1400 | .id_result = result_id, |
| 1405 | 1401 | .storage_class = storage_class, |
| ... | ... | @@ -1643,7 +1639,11 @@ const NavGen = struct { |
| 1643 | 1639 | return try self.arrayType(1, elem_ty_id); |
| 1644 | 1640 | } else { |
| 1645 | 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 | 1647 | return result_id; |
| 1648 | 1648 | } |
| 1649 | 1649 | }, |
| ... | ... | @@ -1662,7 +1662,7 @@ const NavGen = struct { |
| 1662 | 1662 | else => unreachable, |
| 1663 | 1663 | } |
| 1664 | 1664 | |
| 1665 | // Guaranteed by callConvSupportsVarArgs, there are nog SPIR-V CCs which support | |
| 1665 | // Guaranteed by callConvSupportsVarArgs, there are no SPIR-V CCs which support | |
| 1666 | 1666 | // varargs. |
| 1667 | 1667 | assert(!fn_info.is_var_args); |
| 1668 | 1668 | |
| ... | ... | @@ -1698,8 +1698,15 @@ const NavGen = struct { |
| 1698 | 1698 | .pointer => { |
| 1699 | 1699 | const ptr_info = ty.ptrInfo(zcu); |
| 1700 | 1700 | |
| 1701 | const child_ty = Type.fromInterned(ptr_info.child); | |
| 1701 | 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 | } | |
| 1703 | 1710 | |
| 1704 | 1711 | if (ptr_info.flags.size != .Slice) { |
| 1705 | 1712 | return ptr_ty_id; |
| ... | ... | @@ -1746,6 +1753,10 @@ const NavGen = struct { |
| 1746 | 1753 | defer self.gpa.free(type_name); |
| 1747 | 1754 | try self.spv.debugName(result_id, type_name); |
| 1748 | 1755 | |
| 1756 | if (target.os.tag == .vulkan) { | |
| 1757 | try self.spv.decorate(result_id, .Block); // Decorate all structs as block for now... | |
| 1758 | } | |
| 1759 | ||
| 1749 | 1760 | return result_id; |
| 1750 | 1761 | }, |
| 1751 | 1762 | .struct_type => ip.loadStructType(ty.toIntern()), |
| ... | ... | @@ -1791,6 +1802,10 @@ const NavGen = struct { |
| 1791 | 1802 | defer self.gpa.free(type_name); |
| 1792 | 1803 | try self.spv.debugName(result_id, type_name); |
| 1793 | 1804 | |
| 1805 | if (target.os.tag == .vulkan) { | |
| 1806 | try self.spv.decorate(result_id, .Block); // Decorate all structs as block for now... | |
| 1807 | } | |
| 1808 | ||
| 1794 | 1809 | return result_id; |
| 1795 | 1810 | }, |
| 1796 | 1811 | .optional => { |
| ... | ... | @@ -1882,7 +1897,7 @@ const NavGen = struct { |
| 1882 | 1897 | else => unreachable, |
| 1883 | 1898 | }, |
| 1884 | 1899 | .shared => .Workgroup, |
| 1885 | .local => .Private, | |
| 1900 | .local => .Function, | |
| 1886 | 1901 | .global => switch (target.os.tag) { |
| 1887 | 1902 | .opencl => .CrossWorkgroup, |
| 1888 | 1903 | .vulkan => .PhysicalStorageBuffer, |
| ... | ... | @@ -1893,6 +1908,7 @@ const NavGen = struct { |
| 1893 | 1908 | .input => .Input, |
| 1894 | 1909 | .output => .Output, |
| 1895 | 1910 | .uniform => .Uniform, |
| 1911 | .storage_buffer => .StorageBuffer, | |
| 1896 | 1912 | .gs, |
| 1897 | 1913 | .fs, |
| 1898 | 1914 | .ss, |
| ... | ... | @@ -4354,13 +4370,24 @@ const NavGen = struct { |
| 4354 | 4370 | defer self.gpa.free(ids); |
| 4355 | 4371 | |
| 4356 | 4372 | const result_id = self.spv.allocId(); |
| 4357 | try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ | |
| 4358 | .id_result_type = result_ty_id, | |
| 4359 | .id_result = result_id, | |
| 4360 | .base = base, | |
| 4361 | .element = element, | |
| 4362 | .indexes = ids, | |
| 4363 | }); | |
| 4373 | const target = self.getTarget(); | |
| 4374 | switch (target.os.tag) { | |
| 4375 | .opencl => try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{ | |
| 4376 | .id_result_type = result_ty_id, | |
| 4377 | .id_result = result_id, | |
| 4378 | .base = base, | |
| 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 | 4391 | return result_id; |
| 4365 | 4392 | } |
| 4366 | 4393 | |
| ... | ... | @@ -6529,6 +6556,13 @@ const NavGen = struct { |
| 6529 | 6556 | return self.todo("implement inline asm with more than 1 output", .{}); |
| 6530 | 6557 | } |
| 6531 | 6558 | |
| 6559 | var as = SpvAssembler{ | |
| 6560 | .gpa = self.gpa, | |
| 6561 | .spv = self.spv, | |
| 6562 | .func = &self.func, | |
| 6563 | }; | |
| 6564 | defer as.deinit(); | |
| 6565 | ||
| 6532 | 6566 | var output_extra_i = extra_i; |
| 6533 | 6567 | for (outputs) |output| { |
| 6534 | 6568 | if (output != .none) { |
| ... | ... | @@ -6541,7 +6575,6 @@ const NavGen = struct { |
| 6541 | 6575 | // TODO: Record output and use it somewhere. |
| 6542 | 6576 | } |
| 6543 | 6577 | |
| 6544 | var input_extra_i = extra_i; | |
| 6545 | 6578 | for (inputs) |input| { |
| 6546 | 6579 | const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]); |
| 6547 | 6580 | const constraint = std.mem.sliceTo(extra_bytes, 0); |
| ... | ... | @@ -6549,8 +6582,63 @@ const NavGen = struct { |
| 6549 | 6582 | // This equation accounts for the fact that even if we have exactly 4 bytes |
| 6550 | 6583 | // for the string, we still use the next u32 for the null terminator. |
| 6551 | 6584 | extra_i += (constraint.len + name.len + (2 + 3)) / 4; |
| 6552 | // TODO: Record input and use it somewhere. | |
| 6553 | _ = input; | |
| 6585 | ||
| 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 | } |
| 6555 | 6643 | |
| 6556 | 6644 | { |
| ... | ... | @@ -6564,27 +6652,7 @@ const NavGen = struct { |
| 6564 | 6652 | |
| 6565 | 6653 | const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len]; |
| 6566 | 6654 | |
| 6567 | var as = SpvAssembler{ | |
| 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) { | |
| 6655 | as.assemble(asm_source) catch |err| switch (err) { | |
| 6588 | 6656 | error.AssembleFail => { |
| 6589 | 6657 | // TODO: For now the compiler only supports a single error message per decl, |
| 6590 | 6658 | // so to translate the possible multiple errors from the assembler, emit |
| ... | ... | @@ -6629,6 +6697,7 @@ const NavGen = struct { |
| 6629 | 6697 | .just_declared, .unresolved_forward_reference => unreachable, |
| 6630 | 6698 | .ty => return self.fail("cannot return spir-v type as value from assembly", .{}), |
| 6631 | 6699 | .value => |ref| return ref, |
| 6700 | .constant => return self.fail("cannot return constant from assembly", .{}), | |
| 6632 | 6701 | } |
| 6633 | 6702 | |
| 6634 | 6703 | // TODO: Multiple results |
src/codegen/spirv/Assembler.zig+91-14| ... | ... | @@ -45,6 +45,9 @@ const Token = struct { |
| 45 | 45 | pipe, |
| 46 | 46 | /// =. |
| 47 | 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, | |
| 48 | 51 | |
| 49 | 52 | fn name(self: Tag) []const u8 { |
| 50 | 53 | return switch (self) { |
| ... | ... | @@ -56,6 +59,7 @@ const Token = struct { |
| 56 | 59 | .string => "<string literal>", |
| 57 | 60 | .pipe => "'|'", |
| 58 | 61 | .equals => "'='", |
| 62 | .placeholder => "<placeholder>", | |
| 59 | 63 | }; |
| 60 | 64 | } |
| 61 | 65 | }; |
| ... | ... | @@ -128,12 +132,19 @@ const AsmValue = union(enum) { |
| 128 | 132 | /// This result-value represents a type registered into the module's type system. |
| 129 | 133 | ty: IdRef, |
| 130 | 134 | |
| 135 | /// This is a pre-supplied constant integer value. | |
| 136 | constant: u32, | |
| 137 | ||
| 131 | 138 | /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue |
| 132 | 139 | /// is of a variant that allows the result to be obtained (not an unresolved |
| 133 | 140 | /// forward declaration, not in the process of being declared, etc). |
| 134 | 141 | pub fn resultId(self: AsmValue) IdRef { |
| 135 | 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 | 148 | .value => |result| result, |
| 138 | 149 | .ty => |result| result, |
| 139 | 150 | }; |
| ... | ... | @@ -151,7 +162,8 @@ gpa: Allocator, |
| 151 | 162 | errors: std.ArrayListUnmanaged(ErrorMsg) = .empty, |
| 152 | 163 | |
| 153 | 164 | /// The source code that is being assembled. |
| 154 | src: []const u8, | |
| 165 | /// This is set when calling `assemble()`. | |
| 166 | src: []const u8 = undefined, | |
| 155 | 167 | |
| 156 | 168 | /// The module that this assembly is associated to. |
| 157 | 169 | /// Instructions like OpType*, OpDecorate, etc are emitted into this module. |
| ... | ... | @@ -211,7 +223,10 @@ pub fn deinit(self: *Assembler) void { |
| 211 | 223 | self.instruction_map.deinit(self.gpa); |
| 212 | 224 | } |
| 213 | 225 | |
| 214 | pub fn assemble(self: *Assembler) Error!void { | |
| 226 | pub fn assemble(self: *Assembler, src: []const u8) Error!void { | |
| 227 | self.src = src; | |
| 228 | self.errors.clearRetainingCapacity(); | |
| 229 | ||
| 215 | 230 | // Populate the opcode map if it isn't already |
| 216 | 231 | if (self.instruction_map.count() == 0) { |
| 217 | 232 | const instructions = spec.InstructionSet.core.instructions(); |
| ... | ... | @@ -369,6 +384,7 @@ fn processTypeInstruction(self: *Assembler) !AsmValue { |
| 369 | 384 | /// - Function-local instructions are emitted in `self.func`. |
| 370 | 385 | fn processGenericInstruction(self: *Assembler) !?AsmValue { |
| 371 | 386 | const operands = self.inst.operands.items; |
| 387 | var maybe_spv_decl_index: ?SpvModule.Decl.Index = null; | |
| 372 | 388 | const section = switch (self.inst.opcode.class()) { |
| 373 | 389 | .ConstantCreation => &self.spv.sections.types_globals_constants, |
| 374 | 390 | .Annotation => &self.spv.sections.annotations, |
| ... | ... | @@ -378,13 +394,16 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { |
| 378 | 394 | .OpExecutionMode, .OpExecutionModeId => &self.spv.sections.execution_modes, |
| 379 | 395 | .OpVariable => switch (@as(spec.StorageClass, @enumFromInt(operands[2].value))) { |
| 380 | 396 | .Function => &self.func.prologue, |
| 381 | .UniformConstant => &self.spv.sections.types_globals_constants, | |
| 382 | else => { | |
| 383 | // This is currently disabled because global variables are required to be | |
| 384 | // emitted in the proper order, and this should be honored in inline assembly | |
| 385 | // as well. | |
| 386 | return self.todo("global variables", .{}); | |
| 397 | .Input, .Output => section: { | |
| 398 | maybe_spv_decl_index = try self.spv.allocDecl(.global); | |
| 399 | try self.func.decl_deps.put(self.spv.gpa, maybe_spv_decl_index.?, {}); | |
| 400 | // TODO: In theory this can be non-empty if there is an initializer which depends on another global... | |
| 401 | try self.spv.declareDeclDeps(maybe_spv_decl_index.?, &.{}); | |
| 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 | 408 | // Default case - to be worked out further. |
| 390 | 409 | else => &self.func.body, |
| ... | ... | @@ -409,7 +428,10 @@ fn processGenericInstruction(self: *Assembler) !?AsmValue { |
| 409 | 428 | section.writeDoubleWord(dword); |
| 410 | 429 | }, |
| 411 | 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 | 435 | try section.ensureUnusedCapacity(self.spv.gpa, 1); |
| 414 | 436 | section.writeOperand(IdResult, maybe_result_id.?); |
| 415 | 437 | }, |
| ... | ... | @@ -475,8 +497,8 @@ fn resolveRefId(self: *Assembler, ref: AsmValue.Ref) !IdRef { |
| 475 | 497 | /// error message has been emitted into `self.errors`. |
| 476 | 498 | fn parseInstruction(self: *Assembler) !void { |
| 477 | 499 | self.inst.opcode = undefined; |
| 478 | self.inst.operands.shrinkRetainingCapacity(0); | |
| 479 | self.inst.string_bytes.shrinkRetainingCapacity(0); | |
| 500 | self.inst.operands.clearRetainingCapacity(); | |
| 501 | self.inst.string_bytes.clearRetainingCapacity(); | |
| 480 | 502 | |
| 481 | 503 | const lhs_result_tok = self.currentToken(); |
| 482 | 504 | const maybe_lhs_result: ?AsmValue.Ref = if (self.eatToken(.result_id_assign)) blk: { |
| ... | ... | @@ -654,6 +676,22 @@ fn parseRefId(self: *Assembler) !void { |
| 654 | 676 | |
| 655 | 677 | fn parseLiteralInteger(self: *Assembler) !void { |
| 656 | 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 | 695 | try self.expectToken(.value); |
| 658 | 696 | // According to the SPIR-V machine readable grammar, a LiteralInteger |
| 659 | 697 | // 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 | 707 | |
| 670 | 708 | fn parseLiteralExtInstInteger(self: *Assembler) !void { |
| 671 | 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 | 726 | try self.expectToken(.value); |
| 673 | 727 | const text = self.tokenText(tok); |
| 674 | 728 | const value = std.fmt.parseInt(u32, text, 0) catch { |
| ... | ... | @@ -745,6 +799,22 @@ fn parseContextDependentNumber(self: *Assembler) !void { |
| 745 | 799 | |
| 746 | 800 | fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void { |
| 747 | 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 | 818 | try self.expectToken(.value); |
| 749 | 819 | |
| 750 | 820 | if (width == 0 or width > 2 * @bitSizeOf(spec.Word)) { |
| ... | ... | @@ -848,6 +918,8 @@ fn tokenText(self: Assembler, tok: Token) []const u8 { |
| 848 | 918 | /// Tokenize `self.src` and put the tokens in `self.tokens`. |
| 849 | 919 | /// Any errors encountered are appended to `self.errors`. |
| 850 | 920 | fn tokenize(self: *Assembler) !void { |
| 921 | self.tokens.clearRetainingCapacity(); | |
| 922 | ||
| 851 | 923 | var offset: u32 = 0; |
| 852 | 924 | while (true) { |
| 853 | 925 | const tok = try self.nextToken(offset); |
| ... | ... | @@ -890,6 +962,7 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 890 | 962 | string, |
| 891 | 963 | string_end, |
| 892 | 964 | escape, |
| 965 | placeholder, | |
| 893 | 966 | } = .start; |
| 894 | 967 | var token_start = start_offset; |
| 895 | 968 | var offset = start_offset; |
| ... | ... | @@ -917,6 +990,10 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 917 | 990 | offset += 1; |
| 918 | 991 | break; |
| 919 | 992 | }, |
| 993 | '$' => { | |
| 994 | state = .placeholder; | |
| 995 | tag = .placeholder; | |
| 996 | }, | |
| 920 | 997 | else => { |
| 921 | 998 | state = .value; |
| 922 | 999 | tag = .value; |
| ... | ... | @@ -932,11 +1009,11 @@ fn nextToken(self: *Assembler, start_offset: u32) !Token { |
| 932 | 1009 | ' ', '\t', '\r', '\n', '=', '|' => break, |
| 933 | 1010 | else => {}, |
| 934 | 1011 | }, |
| 935 | .result_id => switch (c) { | |
| 1012 | .result_id, .placeholder => switch (c) { | |
| 936 | 1013 | '_', 'a'...'z', 'A'...'Z', '0'...'9' => {}, |
| 937 | 1014 | ' ', '\t', '\r', '\n', '=', '|' => break, |
| 938 | 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 | 1017 | // Again, probably a forgotten delimiter here. |
| 941 | 1018 | break; |
| 942 | 1019 | }, |
src/link/SpirV.zig+1-1| ... | ... | @@ -296,7 +296,7 @@ fn writeCapabilities(spv: *SpvModule, target: std.Target) !void { |
| 296 | 296 | // TODO: Integrate with a hypothetical feature system |
| 297 | 297 | const caps: []const spec.Capability = switch (target.os.tag) { |
| 298 | 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 | 300 | else => unreachable, |
| 301 | 301 | }; |
| 302 | 302 |
src/link/SpirV/deduplicate.zig+8| ... | ... | @@ -511,6 +511,14 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule, progress: std.Pr |
| 511 | 511 | } |
| 512 | 512 | |
| 513 | 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 | 522 | const id: ResultId = @enumFromInt(operand.*); |
| 515 | 523 | const index = info.entities.getIndex(id) orelse continue; |
| 516 | 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 | 458 | .global => false, |
| 459 | 459 | // TODO: Allowed with VK_KHR_variable_pointers. |
| 460 | 460 | .shared => true, |
| 461 | .constant, .local, .input, .output, .uniform, .push_constant => true, | |
| 461 | .constant, .local, .input, .output, .uniform, .push_constant, .storage_buffer => true, | |
| 462 | 462 | else => unreachable, |
| 463 | 463 | }; |
| 464 | 464 | } |