| 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,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 has | 1577 | // 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, |
| 1579 | 1579 |
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, | ||
| 518 | 519 | ||
| 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 | }; |
| 17607 | 17607 | ||
| 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 uninitialized | 37853 | // 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 has | 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,10 +1396,6 @@ const NavGen = struct { |
| 1396 | 1396 | ||
| 1397 | const child_ty_id = try self.resolveType(child_ty, child_repr); | 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 | 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 | } |
| 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 | // varargs. | 1666 | // varargs. |
| 1667 | assert(!fn_info.is_var_args); | 1667 | assert(!fn_info.is_var_args); |
| 1668 | 1668 | ||
| ... | @@ -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); |
| 1700 | 1700 | ||
| 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 | } | ||
| 1703 | 1710 | ||
| 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); |
| 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 | 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); |
| 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 | 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); |
| 4355 | 4371 | ||
| 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 | } |
| 4366 | 4393 | ||
| ... | @@ -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 | } |
| 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 | 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 | } |
| 6543 | 6577 | ||
| 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 bytes | 6582 | // 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 | } |
| 6555 | 6643 | ||
| 6556 | { | 6644 | { |
| ... | @@ -6564,27 +6652,7 @@ const NavGen = struct { | ... | @@ -6564,27 +6652,7 @@ const NavGen = struct { |
| 6564 | 6652 | ||
| 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]; |
| 6566 | 6654 | ||
| 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, emit | 6658 | // 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 | } |
| 6633 | 6702 | ||
| 6634 | // TODO: Multiple results | 6703 | // 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, | ||
| 48 | 51 | ||
| 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, |
| 130 | 134 | ||
| 135 | /// This is a pre-supplied constant integer value. | ||
| 136 | constant: u32, | ||
| 137 | |||
| 131 | /// Retrieve the result-id of this AsmValue. Asserts that this AsmValue | 138 | /// 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 unresolved | 139 | /// 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, |
| 151 | errors: std.ArrayListUnmanaged(ErrorMsg) = .empty, | 162 | errors: std.ArrayListUnmanaged(ErrorMsg) = .empty, |
| 152 | 163 | ||
| 153 | /// The source code that is being assembled. | 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 | /// 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 | } |
| 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 | // Populate the opcode map if it isn't already | 230 | // 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`. |
| 370 | fn processGenericInstruction(self: *Assembler) !?AsmValue { | 385 | fn 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 be | 399 | 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 assembly | 400 | // 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`. |
| 476 | fn parseInstruction(self: *Assembler) !void { | 498 | fn 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(); |
| 480 | 502 | ||
| 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 { |
| 654 | 676 | ||
| 655 | fn parseLiteralInteger(self: *Assembler) !void { | 677 | fn 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 LiteralInteger | 696 | // 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 there | 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,6 +707,22 @@ fn parseLiteralInteger(self: *Assembler) !void { |
| 669 | 707 | ||
| 670 | fn parseLiteralExtInstInteger(self: *Assembler) !void { | 708 | fn 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 { |
| 745 | 799 | ||
| 746 | fn parseContextDependentInt(self: *Assembler, signedness: std.builtin.Signedness, width: u32) !void { | 800 | fn 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); |
| 749 | 819 | ||
| 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`. |
| 850 | fn tokenize(self: *Assembler) !void { | 920 | fn 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 system | 296 | // 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 | }; |
| 302 | 302 |
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 | } |
| 512 | 512 | ||
| 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 | } |