authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2025-02-14 20:28:36+03:30
committergravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2025-02-18 18:07:48+03:30
log85169bbba24d7e7592a24de5af6743b34bfe5961
treef60042eb240c9f01f153d67ee0b7387701cf68f6
parent1b0c7f51ef518d0033dc4cc3fc7088746d9088ac
signaturelock-open Commit is signed but in an unrecognized format.

spirv: respect cpu features


3 files changed, 132 insertions(+), 153 deletions(-)

src/codegen/spirv.zig+56-97
...@@ -176,10 +176,10 @@ pub const Object = struct {...@@ -176,10 +176,10 @@ pub const Object = struct {
176 push_constant_ptr: SpvModule.Decl.Index,176 push_constant_ptr: SpvModule.Decl.Index,
177 } = null,177 } = null,
178178
179 pub fn init(gpa: Allocator) Object {179 pub fn init(gpa: Allocator, target: std.Target) Object {
180 return .{180 return .{
181 .gpa = gpa,181 .gpa = gpa,
182 .spv = SpvModule.init(gpa),182 .spv = SpvModule.init(gpa, target),
183 };183 };
184 }184 }
185185
...@@ -412,11 +412,6 @@ const NavGen = struct {...@@ -412,11 +412,6 @@ const NavGen = struct {
412 self.func.deinit(self.gpa);412 self.func.deinit(self.gpa);
413 }413 }
414414
415 /// Return the target which we are currently compiling for.
416 pub fn getTarget(self: *NavGen) std.Target {
417 return self.pt.zcu.getTarget();
418 }
419
420 pub fn fail(self: *NavGen, comptime format: []const u8, args: anytype) Error {415 pub fn fail(self: *NavGen, comptime format: []const u8, args: anytype) Error {
421 @branchHint(.cold);416 @branchHint(.cold);
422 const zcu = self.pt.zcu;417 const zcu = self.pt.zcu;
...@@ -431,12 +426,12 @@ const NavGen = struct {...@@ -431,12 +426,12 @@ const NavGen = struct {
431 }426 }
432427
433 /// This imports the "default" extended instruction set for the target428 /// This imports the "default" extended instruction set for the target
434 /// For OpenCL, OpenCL.std.100. For Vulkan, GLSL.std.450.429 /// For OpenCL, OpenCL.std.100. For Vulkan and OpenGL, GLSL.std.450.
435 fn importExtendedSet(self: *NavGen) !IdResult {430 fn importExtendedSet(self: *NavGen) !IdResult {
436 const target = self.getTarget();431 const target = self.spv.target;
437 return switch (target.os.tag) {432 return switch (target.os.tag) {
438 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),433 .opencl => try self.spv.importInstructionSet(.@"OpenCL.std"),
439 .vulkan => try self.spv.importInstructionSet(.@"GLSL.std.450"),434 .vulkan, .opengl => try self.spv.importInstructionSet(.@"GLSL.std.450"),
440 else => unreachable,435 else => unreachable,
441 };436 };
442 }437 }
...@@ -546,14 +541,10 @@ const NavGen = struct {...@@ -546,14 +541,10 @@ const NavGen = struct {
546 }541 }
547542
548 fn addFunctionDep(self: *NavGen, decl_index: SpvModule.Decl.Index, storage_class: StorageClass) !void {543 fn addFunctionDep(self: *NavGen, decl_index: SpvModule.Decl.Index, storage_class: StorageClass) !void {
549 const target = self.getTarget();544 if (self.spv.version.minor < 4) {
550 if (target.os.tag == .vulkan) {545 // Before version 1.4, the interface’s storage classes are limited to the Input and Output
551 // Shader entry point dependencies must be variables with Input or Output storage class546 if (storage_class == .Input or storage_class == .Output) {
552 switch (storage_class) {547 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});
553 .Input, .Output => {
554 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});
555 },
556 else => {},
557 }548 }
558 } else {549 } else {
559 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});550 try self.func.decl_deps.put(self.spv.gpa, decl_index, {});
...@@ -561,11 +552,7 @@ const NavGen = struct {...@@ -561,11 +552,7 @@ const NavGen = struct {
561 }552 }
562553
563 fn castToGeneric(self: *NavGen, type_id: IdRef, ptr_id: IdRef) !IdRef {554 fn castToGeneric(self: *NavGen, type_id: IdRef, ptr_id: IdRef) !IdRef {
564 const target = self.getTarget();555 if (self.spv.hasFeature(.Kernel)) {
565
566 if (target.os.tag == .vulkan) {
567 return ptr_id;
568 } else {
569 const result_id = self.spv.allocId();556 const result_id = self.spv.allocId();
570 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{557 try self.func.body.emit(self.spv.gpa, .OpPtrCastToGeneric, .{
571 .id_result_type = type_id,558 .id_result_type = type_id,
...@@ -574,6 +561,8 @@ const NavGen = struct {...@@ -574,6 +561,8 @@ const NavGen = struct {
574 });561 });
575 return result_id;562 return result_id;
576 }563 }
564
565 return ptr_id;
577 }566 }
578567
579 /// Start a new SPIR-V block, Emits the label of the new block, and stores which568 /// Start a new SPIR-V block, Emits the label of the new block, and stores which
...@@ -596,8 +585,6 @@ const NavGen = struct {...@@ -596,8 +585,6 @@ const NavGen = struct {
596 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).585 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
597 /// TODO: Should the result of this function be cached?586 /// TODO: Should the result of this function be cached?
598 fn backingIntBits(self: *NavGen, bits: u16) ?u16 {587 fn backingIntBits(self: *NavGen, bits: u16) ?u16 {
599 const target = self.getTarget();
600
601 // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.588 // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.
602 assert(bits != 0);589 assert(bits != 0);
603590
...@@ -611,14 +598,8 @@ const NavGen = struct {...@@ -611,14 +598,8 @@ const NavGen = struct {
611 };598 };
612599
613 for (ints) |int| {600 for (ints) |int| {
614 const has_feature = if (int.feature) |feature|601 const has_feature = if (int.feature) |feature| self.spv.hasFeature(feature) else true;
615 Target.spirv.featureSetHas(target.cpu.features, feature)602 if (bits <= int.bits and has_feature) return int.bits;
616 else
617 true;
618
619 if (bits <= int.bits and has_feature) {
620 return int.bits;
621 }
622 }603 }
623604
624 return null;605 return null;
...@@ -631,11 +612,7 @@ const NavGen = struct {...@@ -631,11 +612,7 @@ const NavGen = struct {
631 /// is no way of knowing whether those are actually supported.612 /// is no way of knowing whether those are actually supported.
632 /// TODO: Maybe this should be cached?613 /// TODO: Maybe this should be cached?
633 fn largestSupportedIntBits(self: *NavGen) u16 {614 fn largestSupportedIntBits(self: *NavGen) u16 {
634 const target = self.getTarget();615 return if (self.spv.hasFeature(.Int64)) 64 else 32;
635 return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
636 64
637 else
638 32;
639 }616 }
640617
641 /// Checks whether the type is "composite int", an integer consisting of multiple native integers. These are represented by618 /// Checks whether the type is "composite int", an integer consisting of multiple native integers. These are represented by
...@@ -648,7 +625,6 @@ const NavGen = struct {...@@ -648,7 +625,6 @@ const NavGen = struct {
648 /// Checks whether the type can be directly translated to SPIR-V vectors625 /// Checks whether the type can be directly translated to SPIR-V vectors
649 fn isSpvVector(self: *NavGen, ty: Type) bool {626 fn isSpvVector(self: *NavGen, ty: Type) bool {
650 const zcu = self.pt.zcu;627 const zcu = self.pt.zcu;
651 const target = self.getTarget();
652 if (ty.zigTypeTag(zcu) != .vector) return false;628 if (ty.zigTypeTag(zcu) != .vector) return false;
653629
654 // TODO: This check must be expanded for types that can be represented630 // TODO: This check must be expanded for types that can be represented
...@@ -664,17 +640,19 @@ const NavGen = struct {...@@ -664,17 +640,19 @@ const NavGen = struct {
664 }640 }
665641
666 const elem_ty = ty.childType(zcu);642 const elem_ty = ty.childType(zcu);
667
668 const len = ty.vectorLen(zcu);643 const len = ty.vectorLen(zcu);
669 const is_scalar = elem_ty.isNumeric(zcu) or elem_ty.toIntern() == .bool_type;644
670 const spirv_len = len > 1 and len <= 4;645 if (elem_ty.isNumeric(zcu) or elem_ty.toIntern() == .bool_type) {
671 const opencl_len = if (target.os.tag == .opencl) (len == 8 or len == 16) else false;646 if (len > 1 and len <= 4) return true;
672 return is_scalar and (spirv_len or opencl_len);647 if (self.spv.hasFeature(.Vector16)) return (len == 8 or len == 16);
648 }
649
650 return false;
673 }651 }
674652
675 fn arithmeticTypeInfo(self: *NavGen, ty: Type) ArithmeticTypeInfo {653 fn arithmeticTypeInfo(self: *NavGen, ty: Type) ArithmeticTypeInfo {
676 const zcu = self.pt.zcu;654 const zcu = self.pt.zcu;
677 const target = self.getTarget();655 const target = self.spv.target;
678 var scalar_ty = ty.scalarType(zcu);656 var scalar_ty = ty.scalarType(zcu);
679 if (scalar_ty.zigTypeTag(zcu) == .@"enum") {657 if (scalar_ty.zigTypeTag(zcu) == .@"enum") {
680 scalar_ty = scalar_ty.intTagType(zcu);658 scalar_ty = scalar_ty.intTagType(zcu);
...@@ -791,7 +769,7 @@ const NavGen = struct {...@@ -791,7 +769,7 @@ const NavGen = struct {
791 /// ty must be an aggregate type.769 /// ty must be an aggregate type.
792 fn constructCompositeSplat(self: *NavGen, ty: Type, constituent: IdRef) !IdRef {770 fn constructCompositeSplat(self: *NavGen, ty: Type, constituent: IdRef) !IdRef {
793 const zcu = self.pt.zcu;771 const zcu = self.pt.zcu;
794 const n = ty.arrayLen(zcu);772 const n: usize = @intCast(ty.arrayLen(zcu));
795773
796 const constituents = try self.gpa.alloc(IdRef, n);774 const constituents = try self.gpa.alloc(IdRef, n);
797 defer self.gpa.free(constituents);775 defer self.gpa.free(constituents);
...@@ -817,7 +795,7 @@ const NavGen = struct {...@@ -817,7 +795,7 @@ const NavGen = struct {
817795
818 const pt = self.pt;796 const pt = self.pt;
819 const zcu = pt.zcu;797 const zcu = pt.zcu;
820 const target = self.getTarget();798 const target = self.spv.target;
821 const result_ty_id = try self.resolveType(ty, repr);799 const result_ty_id = try self.resolveType(ty, repr);
822 const ip = &zcu.intern_pool;800 const ip = &zcu.intern_pool;
823801
...@@ -1263,11 +1241,11 @@ const NavGen = struct {...@@ -1263,11 +1241,11 @@ const NavGen = struct {
1263 };1241 };
12641242
1265 // Kernel only supports unsigned ints.1243 // Kernel only supports unsigned ints.
1266 if (self.getTarget().os.tag == .vulkan) {1244 if (self.spv.hasFeature(.Kernel)) {
1267 return self.spv.intType(signedness, backing_bits);1245 return self.spv.intType(.unsigned, backing_bits);
1268 }1246 }
12691247
1270 return self.spv.intType(.unsigned, backing_bits);1248 return self.spv.intType(signedness, backing_bits);
1271 }1249 }
12721250
1273 fn arrayType(self: *NavGen, len: u32, child_ty: IdRef) !IdRef {1251 fn arrayType(self: *NavGen, len: u32, child_ty: IdRef) !IdRef {
...@@ -1436,7 +1414,7 @@ const NavGen = struct {...@@ -1436,7 +1414,7 @@ const NavGen = struct {
1436 const zcu = pt.zcu;1414 const zcu = pt.zcu;
1437 const ip = &zcu.intern_pool;1415 const ip = &zcu.intern_pool;
1438 log.debug("resolveType: ty = {}", .{ty.fmt(pt)});1416 log.debug("resolveType: ty = {}", .{ty.fmt(pt)});
1439 const target = self.getTarget();1417 const target = self.spv.target;
14401418
1441 const section = &self.spv.sections.types_globals_constants;1419 const section = &self.spv.sections.types_globals_constants;
14421420
...@@ -1533,7 +1511,7 @@ const NavGen = struct {...@@ -1533,7 +1511,7 @@ const NavGen = struct {
1533 return try self.arrayType(1, elem_ty_id);1511 return try self.arrayType(1, elem_ty_id);
1534 } else {1512 } else {
1535 const result_id = try self.arrayType(total_len, elem_ty_id);1513 const result_id = try self.arrayType(total_len, elem_ty_id);
1536 if (target.os.tag == .vulkan) {1514 if (self.spv.hasFeature(.Shader)) {
1537 try self.spv.decorate(result_id, .{ .ArrayStride = .{1515 try self.spv.decorate(result_id, .{ .ArrayStride = .{
1538 .array_stride = @intCast(elem_ty.abiSize(zcu)),1516 .array_stride = @intCast(elem_ty.abiSize(zcu)),
1539 } });1517 } });
...@@ -1667,7 +1645,7 @@ const NavGen = struct {...@@ -1667,7 +1645,7 @@ const NavGen = struct {
1667 continue;1645 continue;
1668 }1646 }
16691647
1670 if (target.os.tag == .vulkan) {1648 if (self.spv.hasFeature(.Shader)) {
1671 try self.spv.decorateMember(result_id, index, .{ .Offset = .{1649 try self.spv.decorateMember(result_id, index, .{ .Offset = .{
1672 .byte_offset = @intCast(ty.structFieldOffset(field_index, zcu)),1650 .byte_offset = @intCast(ty.structFieldOffset(field_index, zcu)),
1673 } });1651 } });
...@@ -1769,20 +1747,11 @@ const NavGen = struct {...@@ -1769,20 +1747,11 @@ const NavGen = struct {
1769 }1747 }
17701748
1771 fn spvStorageClass(self: *NavGen, as: std.builtin.AddressSpace) StorageClass {1749 fn spvStorageClass(self: *NavGen, as: std.builtin.AddressSpace) StorageClass {
1772 const target = self.getTarget();
1773 return switch (as) {1750 return switch (as) {
1774 .generic => switch (target.os.tag) {1751 .generic => if (self.spv.hasFeature(.GenericPointer)) .Generic else .Function,
1775 .vulkan => .Function,
1776 .opencl => .Generic,
1777 else => unreachable,
1778 },
1779 .shared => .Workgroup,1752 .shared => .Workgroup,
1780 .local => .Function,1753 .local => .Function,
1781 .global => switch (target.os.tag) {1754 .global => if (self.spv.hasFeature(.Shader)) .PhysicalStorageBuffer else .CrossWorkgroup,
1782 .opencl => .CrossWorkgroup,
1783 .vulkan => .PhysicalStorageBuffer,
1784 else => unreachable,
1785 },
1786 .constant => .UniformConstant,1755 .constant => .UniformConstant,
1787 .push_constant => .PushConstant,1756 .push_constant => .PushConstant,
1788 .input => .Input,1757 .input => .Input,
...@@ -2326,7 +2295,7 @@ const NavGen = struct {...@@ -2326,7 +2295,7 @@ const NavGen = struct {
2326 }2295 }
23272296
2328 fn buildFma(self: *NavGen, a: Temporary, b: Temporary, c: Temporary) !Temporary {2297 fn buildFma(self: *NavGen, a: Temporary, b: Temporary, c: Temporary) !Temporary {
2329 const target = self.getTarget();2298 const target = self.spv.target;
23302299
2331 const v = self.vectorization(.{ a, b, c });2300 const v = self.vectorization(.{ a, b, c });
2332 const ops = v.operations();2301 const ops = v.operations();
...@@ -2348,7 +2317,7 @@ const NavGen = struct {...@@ -2348,7 +2317,7 @@ const NavGen = struct {
2348 // NOTE: Vulkan's FMA instruction does *NOT* produce the right values!2317 // NOTE: Vulkan's FMA instruction does *NOT* produce the right values!
2349 // its precision guarantees do NOT match zigs and it does NOT match OpenCLs!2318 // its precision guarantees do NOT match zigs and it does NOT match OpenCLs!
2350 // it needs to be emulated!2319 // it needs to be emulated!
2351 .vulkan => unreachable, // TODO: See above2320 .vulkan, .opengl => unreachable, // TODO: See above
2352 else => unreachable,2321 else => unreachable,
2353 };2322 };
23542323
...@@ -2485,14 +2454,14 @@ const NavGen = struct {...@@ -2485,14 +2454,14 @@ const NavGen = struct {
2485 };2454 };
24862455
2487 fn buildUnary(self: *NavGen, op: UnaryOp, operand: Temporary) !Temporary {2456 fn buildUnary(self: *NavGen, op: UnaryOp, operand: Temporary) !Temporary {
2488 const target = self.getTarget();2457 const target = self.spv.target;
2489 const v = blk: {2458 const v = blk: {
2490 const v = self.vectorization(.{operand});2459 const v = self.vectorization(.{operand});
2491 break :blk switch (op) {2460 break :blk switch (op) {
2492 // TODO: These instructions don't seem to be working2461 // TODO: These instructions don't seem to be working
2493 // properly for LLVM-based backends on OpenCL for 8- and2462 // properly for LLVM-based backends on OpenCL for 8- and
2494 // 16-component vectors.2463 // 16-component vectors.
2495 .i_abs => if (target.os.tag == .opencl and v.components() >= 8) v.unroll() else v,2464 .i_abs => if (self.spv.hasFeature(.Vector16) and v.components() >= 8) v.unroll() else v,
2496 else => v,2465 else => v,
2497 };2466 };
2498 };2467 };
...@@ -2545,7 +2514,7 @@ const NavGen = struct {...@@ -2545,7 +2514,7 @@ const NavGen = struct {
2545 // Note: We'll need to check these for floating point accuracy2514 // Note: We'll need to check these for floating point accuracy
2546 // Vulkan does not put tight requirements on these, for correction2515 // Vulkan does not put tight requirements on these, for correction
2547 // we might want to emulate them at some point.2516 // we might want to emulate them at some point.
2548 .vulkan => switch (op) {2517 .vulkan, .opengl => switch (op) {
2549 .i_abs => 5, // SAbs2518 .i_abs => 5, // SAbs
2550 .f_abs => 4, // FAbs2519 .f_abs => 4, // FAbs
2551 .clz => unreachable, // TODO2520 .clz => unreachable, // TODO
...@@ -2615,7 +2584,7 @@ const NavGen = struct {...@@ -2615,7 +2584,7 @@ const NavGen = struct {
2615 };2584 };
26162585
2617 fn buildBinary(self: *NavGen, op: BinaryOp, lhs: Temporary, rhs: Temporary) !Temporary {2586 fn buildBinary(self: *NavGen, op: BinaryOp, lhs: Temporary, rhs: Temporary) !Temporary {
2618 const target = self.getTarget();2587 const target = self.spv.target;
26192588
2620 const v = self.vectorization(.{ lhs, rhs });2589 const v = self.vectorization(.{ lhs, rhs });
2621 const ops = v.operations();2590 const ops = v.operations();
...@@ -2674,7 +2643,7 @@ const NavGen = struct {...@@ -2674,7 +2643,7 @@ const NavGen = struct {
2674 .u_min => 159, // u_min2643 .u_min => 159, // u_min
2675 else => unreachable,2644 else => unreachable,
2676 },2645 },
2677 .vulkan => switch (op) {2646 .vulkan, .opengl => switch (op) {
2678 .f_max => 40, // FMax2647 .f_max => 40, // FMax
2679 .s_max => 42, // SMax2648 .s_max => 42, // SMax
2680 .u_max => 41, // UMax2649 .u_max => 41, // UMax
...@@ -2713,7 +2682,7 @@ const NavGen = struct {...@@ -2713,7 +2682,7 @@ const NavGen = struct {
2713 ) !struct { Temporary, Temporary } {2682 ) !struct { Temporary, Temporary } {
2714 const pt = self.pt;2683 const pt = self.pt;
2715 const zcu = pt.zcu;2684 const zcu = pt.zcu;
2716 const target = self.getTarget();2685 const target = self.spv.target;
2717 const ip = &zcu.intern_pool;2686 const ip = &zcu.intern_pool;
27182687
2719 const v = lhs.vectorization(self).unify(rhs.vectorization(self));2688 const v = lhs.vectorization(self).unify(rhs.vectorization(self));
...@@ -2756,7 +2725,7 @@ const NavGen = struct {...@@ -2756,7 +2725,7 @@ const NavGen = struct {
2756 });2725 });
2757 }2726 }
2758 },2727 },
2759 .vulkan => {2728 .vulkan, .opengl => {
2760 // Operations return a struct{T, T}2729 // Operations return a struct{T, T}
2761 // where T is maybe vectorized.2730 // where T is maybe vectorized.
2762 const op_result_ty: Type = .fromInterned(try ip.getTupleType(zcu.gpa, pt.tid, .{2731 const op_result_ty: Type = .fromInterned(try ip.getTupleType(zcu.gpa, pt.tid, .{
...@@ -2843,7 +2812,7 @@ const NavGen = struct {...@@ -2843,7 +2812,7 @@ const NavGen = struct {
28432812
2844 const section = &self.spv.sections.functions;2813 const section = &self.spv.sections.functions;
28452814
2846 const target = self.getTarget();2815 const target = self.spv.target;
28472816
2848 const p_error_id = self.spv.allocId();2817 const p_error_id = self.spv.allocId();
2849 switch (target.os.tag) {2818 switch (target.os.tag) {
...@@ -2866,7 +2835,7 @@ const NavGen = struct {...@@ -2866,7 +2835,7 @@ const NavGen = struct {
2866 .id_result = self.spv.allocId(),2835 .id_result = self.spv.allocId(),
2867 });2836 });
2868 },2837 },
2869 .vulkan => {2838 .vulkan, .opengl => {
2870 const ptr_ptr_anyerror_ty_id = self.spv.allocId();2839 const ptr_ptr_anyerror_ty_id = self.spv.allocId();
2871 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{2840 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
2872 .id_result = ptr_ptr_anyerror_ty_id,2841 .id_result = ptr_ptr_anyerror_ty_id,
...@@ -2967,7 +2936,7 @@ const NavGen = struct {...@@ -2967,7 +2936,7 @@ const NavGen = struct {
2967 defer self.gpa.free(test_name);2936 defer self.gpa.free(test_name);
29682937
2969 const execution_mode: spec.ExecutionModel = switch (target.os.tag) {2938 const execution_mode: spec.ExecutionModel = switch (target.os.tag) {
2970 .vulkan => .GLCompute,2939 .vulkan, .opengl => .GLCompute,
2971 .opencl => .Kernel,2940 .opencl => .Kernel,
2972 else => unreachable,2941 else => unreachable,
2973 };2942 };
...@@ -3670,7 +3639,6 @@ const NavGen = struct {...@@ -3670,7 +3639,6 @@ const NavGen = struct {
3670 }3639 }
36713640
3672 fn abs(self: *NavGen, result_ty: Type, value: Temporary) !Temporary {3641 fn abs(self: *NavGen, result_ty: Type, value: Temporary) !Temporary {
3673 const target = self.getTarget();
3674 const operand_info = self.arithmeticTypeInfo(value.ty);3642 const operand_info = self.arithmeticTypeInfo(value.ty);
36753643
3676 switch (operand_info.class) {3644 switch (operand_info.class) {
...@@ -3682,7 +3650,7 @@ const NavGen = struct {...@@ -3682,7 +3650,7 @@ const NavGen = struct {
3682 // depending on the result type. Do that when3650 // depending on the result type. Do that when
3683 // bitCast is implemented for vectors.3651 // bitCast is implemented for vectors.
3684 // This is only relevant for Vulkan3652 // This is only relevant for Vulkan
3685 assert(target.os.tag != .vulkan); // TODO3653 assert(self.spv.hasFeature(.Kernel)); // TODO
36863654
3687 return try self.normalize(abs_value, self.arithmeticTypeInfo(result_ty));3655 return try self.normalize(abs_value, self.arithmeticTypeInfo(result_ty));
3688 },3656 },
...@@ -3756,7 +3724,6 @@ const NavGen = struct {...@@ -3756,7 +3724,6 @@ const NavGen = struct {
3756 }3724 }
37573725
3758 fn airMulOverflow(self: *NavGen, inst: Air.Inst.Index) !?IdRef {3726 fn airMulOverflow(self: *NavGen, inst: Air.Inst.Index) !?IdRef {
3759 const target = self.getTarget();
3760 const pt = self.pt;3727 const pt = self.pt;
37613728
3762 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;3729 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
...@@ -3780,7 +3747,7 @@ const NavGen = struct {...@@ -3780,7 +3747,7 @@ const NavGen = struct {
3780 // - Additionally, if info.bits != 32, we'll have to check the high bits3747 // - Additionally, if info.bits != 32, we'll have to check the high bits
3781 // of the result too.3748 // of the result too.
37823749
3783 const largest_int_bits: u16 = if (Target.spirv.featureSetHas(target.cpu.features, .Int64)) 64 else 32;3750 const largest_int_bits = self.largestSupportedIntBits();
3784 // If non-null, the number of bits that the multiplication should be performed in. If3751 // If non-null, the number of bits that the multiplication should be performed in. If
3785 // null, we have to use wide multiplication.3752 // null, we have to use wide multiplication.
3786 const maybe_op_ty_bits: ?u16 = switch (info.bits) {3753 const maybe_op_ty_bits: ?u16 = switch (info.bits) {
...@@ -3989,7 +3956,6 @@ const NavGen = struct {...@@ -3989,7 +3956,6 @@ const NavGen = struct {
3989 if (self.liveness.isUnused(inst)) return null;3956 if (self.liveness.isUnused(inst)) return null;
39903957
3991 const zcu = self.pt.zcu;3958 const zcu = self.pt.zcu;
3992 const target = self.getTarget();
3993 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;3959 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3994 const operand = try self.temporary(ty_op.operand);3960 const operand = try self.temporary(ty_op.operand);
39953961
...@@ -4002,10 +3968,7 @@ const NavGen = struct {...@@ -4002,10 +3968,7 @@ const NavGen = struct {
4002 .float, .bool => unreachable,3968 .float, .bool => unreachable,
4003 }3969 }
40043970
4005 switch (target.os.tag) {3971 assert(self.spv.hasFeature(.Kernel)); // TODO
4006 .vulkan => unreachable, // TODO
4007 else => {},
4008 }
40093972
4010 const count = try self.buildUnary(op, operand);3973 const count = try self.buildUnary(op, operand);
40113974
...@@ -4241,23 +4204,22 @@ const NavGen = struct {...@@ -4241,23 +4204,22 @@ const NavGen = struct {
4241 defer self.gpa.free(ids);4204 defer self.gpa.free(ids);
42424205
4243 const result_id = self.spv.allocId();4206 const result_id = self.spv.allocId();
4244 const target = self.getTarget();4207 if (self.spv.hasFeature(.Kernel)) {
4245 switch (target.os.tag) {4208 try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{
4246 .opencl => try self.func.body.emit(self.spv.gpa, .OpInBoundsPtrAccessChain, .{
4247 .id_result_type = result_ty_id,4209 .id_result_type = result_ty_id,
4248 .id_result = result_id,4210 .id_result = result_id,
4249 .base = base,4211 .base = base,
4250 .element = element,4212 .element = element,
4251 .indexes = ids,4213 .indexes = ids,
4252 }),4214 });
4253 .vulkan => try self.func.body.emit(self.spv.gpa, .OpPtrAccessChain, .{4215 } else {
4216 try self.func.body.emit(self.spv.gpa, .OpPtrAccessChain, .{
4254 .id_result_type = result_ty_id,4217 .id_result_type = result_ty_id,
4255 .id_result = result_id,4218 .id_result = result_id,
4256 .base = base,4219 .base = base,
4257 .element = element,4220 .element = element,
4258 .indexes = ids,4221 .indexes = ids,
4259 }),4222 });
4260 else => unreachable,
4261 }4223 }
4262 return result_id;4224 return result_id;
4263 }4225 }
...@@ -5328,10 +5290,7 @@ const NavGen = struct {...@@ -5328,10 +5290,7 @@ const NavGen = struct {
5328 .initializer = options.initializer,5290 .initializer = options.initializer,
5329 });5291 });
53305292
5331 const target = self.getTarget();5293 if (self.spv.hasFeature(.Shader)) return var_id;
5332 if (target.os.tag == .vulkan) {
5333 return var_id;
5334 }
53355294
5336 switch (options.storage_class) {5295 switch (options.storage_class) {
5337 .Generic => {5296 .Generic => {
...@@ -6204,7 +6163,7 @@ const NavGen = struct {...@@ -6204,7 +6163,7 @@ const NavGen = struct {
6204 fn airSwitchBr(self: *NavGen, inst: Air.Inst.Index) !void {6163 fn airSwitchBr(self: *NavGen, inst: Air.Inst.Index) !void {
6205 const pt = self.pt;6164 const pt = self.pt;
6206 const zcu = pt.zcu;6165 const zcu = pt.zcu;
6207 const target = self.getTarget();6166 const target = self.spv.target;
6208 const switch_br = self.air.unwrapSwitch(inst);6167 const switch_br = self.air.unwrapSwitch(inst);
6209 const cond_ty = self.typeOf(switch_br.operand);6168 const cond_ty = self.typeOf(switch_br.operand);
6210 const cond = try self.resolve(switch_br.operand);6169 const cond = try self.resolve(switch_br.operand);
src/codegen/spirv/Module.zig+27-13
...@@ -118,6 +118,12 @@ gpa: Allocator,...@@ -118,6 +118,12 @@ gpa: Allocator,
118/// Arena for things that need to live for the length of this program.118/// Arena for things that need to live for the length of this program.
119arena: std.heap.ArenaAllocator,119arena: std.heap.ArenaAllocator,
120120
121/// Target info
122target: std.Target,
123
124/// The target SPIR-V version
125version: spec.Version,
126
121/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".127/// Module layout, according to SPIR-V Spec section 2.4, "Logical Layout of a Module".
122sections: struct {128sections: struct {
123 /// Capability instructions129 /// Capability instructions
...@@ -196,10 +202,23 @@ entry_points: std.ArrayListUnmanaged(EntryPoint) = .empty,...@@ -196,10 +202,23 @@ entry_points: std.ArrayListUnmanaged(EntryPoint) = .empty,
196/// The list of extended instruction sets that should be imported.202/// The list of extended instruction sets that should be imported.
197extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, IdRef) = .empty,203extended_instruction_set: std.AutoHashMapUnmanaged(spec.InstructionSet, IdRef) = .empty,
198204
199pub fn init(gpa: Allocator) Module {205pub fn init(gpa: Allocator, target: std.Target) Module {
206 const version_minor: u8 = blk: {
207 // Prefer higher versions
208 if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_6)) break :blk 6;
209 if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_5)) break :blk 5;
210 if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_4)) break :blk 4;
211 if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_3)) break :blk 3;
212 if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_2)) break :blk 2;
213 if (std.Target.spirv.featureSetHas(target.cpu.features, .v1_1)) break :blk 1;
214 break :blk 0;
215 };
216
200 return .{217 return .{
201 .gpa = gpa,218 .gpa = gpa,
202 .arena = std.heap.ArenaAllocator.init(gpa),219 .arena = std.heap.ArenaAllocator.init(gpa),
220 .target = target,
221 .version = .{ .major = 1, .minor = version_minor },
203 .next_result_id = 1, // 0 is an invalid SPIR-V result id, so start counting at 1.222 .next_result_id = 1, // 0 is an invalid SPIR-V result id, so start counting at 1.
204 };223 };
205}224}
...@@ -263,6 +282,10 @@ pub fn idBound(self: Module) Word {...@@ -263,6 +282,10 @@ pub fn idBound(self: Module) Word {
263 return self.next_result_id;282 return self.next_result_id;
264}283}
265284
285pub fn hasFeature(self: *Module, feature: std.Target.spirv.Feature) bool {
286 return std.Target.spirv.featureSetHas(self.target.cpu.features, feature);
287}
288
266fn addEntryPointDeps(289fn addEntryPointDeps(
267 self: *Module,290 self: *Module,
268 decl_index: Decl.Index,291 decl_index: Decl.Index,
...@@ -315,7 +338,7 @@ fn entryPoints(self: *Module) !Section {...@@ -315,7 +338,7 @@ fn entryPoints(self: *Module) !Section {
315 return entry_points;338 return entry_points;
316}339}
317340
318pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {341pub fn finalize(self: *Module, a: Allocator) ![]Word {
319 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"342 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
320 // TODO: Audit calls to allocId() in this function to make it idempotent.343 // TODO: Audit calls to allocId() in this function to make it idempotent.
321344
...@@ -324,16 +347,7 @@ pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {...@@ -324,16 +347,7 @@ pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {
324347
325 const header = [_]Word{348 const header = [_]Word{
326 spec.magic_number,349 spec.magic_number,
327 // TODO: From cpu features350 self.version.toWord(),
328 spec.Version.toWord(.{
329 .major = 1,
330 .minor = switch (target.os.tag) {
331 // Emit SPIR-V 1.3 for now. This is the highest version that Vulkan 1.1 supports.
332 .vulkan => 3,
333 // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
334 else => 4,
335 },
336 }),
337 spec.zig_generator_id,351 spec.zig_generator_id,
338 self.idBound(),352 self.idBound(),
339 0, // Schema (currently reserved for future use)353 0, // Schema (currently reserved for future use)
...@@ -342,7 +356,7 @@ pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {...@@ -342,7 +356,7 @@ pub fn finalize(self: *Module, a: Allocator, target: std.Target) ![]Word {
342 var source = Section{};356 var source = Section{};
343 defer source.deinit(self.gpa);357 defer source.deinit(self.gpa);
344 try self.sections.debug_strings.emit(self.gpa, .OpSource, .{358 try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
345 .source_language = .Unknown,359 .source_language = .Zig,
346 .version = 0,360 .version = 0,
347 // We cannot emit these because the Khronos translator does not parse this instruction361 // We cannot emit these because the Khronos translator does not parse this instruction
348 // correctly.362 // correctly.
src/link/SpirV.zig+49-43
...@@ -75,7 +75,7 @@ pub fn createEmpty(...@@ -75,7 +75,7 @@ pub fn createEmpty(
75 .disable_lld_caching = options.disable_lld_caching,75 .disable_lld_caching = options.disable_lld_caching,
76 .build_id = options.build_id,76 .build_id = options.build_id,
77 },77 },
78 .object = codegen.Object.init(gpa),78 .object = codegen.Object.init(gpa, comp.getTarget()),
79 };79 };
80 errdefer self.deinit();80 errdefer self.deinit();
8181
...@@ -172,7 +172,7 @@ pub fn updateExports(...@@ -172,7 +172,7 @@ pub fn updateExports(
172 const spv_decl_index = try self.object.resolveNav(zcu, nav_index);172 const spv_decl_index = try self.object.resolveNav(zcu, nav_index);
173 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);173 const cc = Type.fromInterned(nav_ty).fnCallingConvention(zcu);
174 const execution_model: spec.ExecutionModel = switch (target.os.tag) {174 const execution_model: spec.ExecutionModel = switch (target.os.tag) {
175 .vulkan => switch (cc) {175 .vulkan, .opengl => switch (cc) {
176 .spirv_vertex => .Vertex,176 .spirv_vertex => .Vertex,
177 .spirv_fragment => .Fragment,177 .spirv_fragment => .Fragment,
178 .spirv_kernel => .GLCompute,178 .spirv_kernel => .GLCompute,
...@@ -231,10 +231,9 @@ pub fn flushModule(...@@ -231,10 +231,9 @@ pub fn flushModule(
231 const spv = &self.object.spv;231 const spv = &self.object.spv;
232 const diags = &comp.link_diags;232 const diags = &comp.link_diags;
233 const gpa = comp.gpa;233 const gpa = comp.gpa;
234 const target = comp.getTarget();
235234
236 try writeCapabilities(spv, target);235 try writeCapabilities(spv);
237 try writeMemoryModel(spv, target);236 try writeMemoryModel(spv);
238237
239 // We need to export the list of error names somewhere so that we can pretty-print them in the238 // We need to export the list of error names somewhere so that we can pretty-print them in the
240 // executor. This is not really an important thing though, so we can just dump it in any old239 // executor. This is not really an important thing though, so we can just dump it in any old
...@@ -269,7 +268,7 @@ pub fn flushModule(...@@ -269,7 +268,7 @@ pub fn flushModule(
269 .extension = error_info.items,268 .extension = error_info.items,
270 });269 });
271270
272 const module = try spv.finalize(arena, target);271 const module = try spv.finalize(arena);
273 errdefer arena.free(module);272 errdefer arena.free(module);
274273
275 const linked_module = self.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {274 const linked_module = self.linkModule(arena, module, sub_prog_node) catch |err| switch (err) {
...@@ -299,56 +298,63 @@ fn linkModule(self: *SpirV, a: Allocator, module: []Word, progress: std.Progress...@@ -299,56 +298,63 @@ fn linkModule(self: *SpirV, a: Allocator, module: []Word, progress: std.Progress
299 return binary.finalize(a);298 return binary.finalize(a);
300}299}
301300
302fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {301fn writeCapabilities(spv: *SpvModule) !void {
303 const gpa = spv.gpa;302 var caps: std.ArrayList(spec.Capability) = .init(spv.gpa);
304 // TODO: Integrate with a hypothetical feature system303 var extensions: std.ArrayList([]const u8) = .init(spv.gpa);
305 const caps: []const spec.Capability = switch (target.os.tag) {304 defer {
306 .opencl => &.{ .Kernel, .Addresses, .Int8, .Int16, .Int64, .Float64, .Float16, .Vector16, .GenericPointer },305 caps.deinit();
307 .vulkan => &.{ .Shader, .PhysicalStorageBufferAddresses, .Int8, .Int16, .Int64, .Float64, .Float16, .VariablePointers, .VariablePointersStorageBuffer },306 extensions.deinit();
308 else => unreachable,307 }
309 };
310308
311 for (caps) |cap| {309 // Currently all spirv target features name are mapped to a Capability or an Extension.
312 try spv.sections.capabilities.emit(gpa, .OpCapability, .{310 // Except for versions which we ignore.
311 for (std.Target.spirv.all_features, 0..) |_, i| {
312 if (spv.target.cpu.features.isEnabled(@intCast(i))) {
313 const feature: std.Target.spirv.Feature = @enumFromInt(i);
314 const name = @tagName(feature);
315 if (std.meta.stringToEnum(spec.Capability, name)) |cap| {
316 try caps.append(cap);
317 } else if (std.mem.startsWith(u8, name, "SPV_")) {
318 try extensions.append(name);
319 }
320 }
321 }
322
323 for (caps.items) |cap| {
324 try spv.sections.capabilities.emit(spv.gpa, .OpCapability, .{
313 .capability = cap,325 .capability = cap,
314 });326 });
315 }327 }
316328
317 switch (target.os.tag) {329 for (extensions.items) |ext| {
318 .vulkan => {330 try spv.sections.extensions.emit(spv.gpa, .OpExtension, .{ .name = ext });
319 try spv.sections.extensions.emit(gpa, .OpExtension, .{
320 .name = "SPV_KHR_physical_storage_buffer",
321 });
322 },
323 else => {},
324 }331 }
325}332}
326333
327fn writeMemoryModel(spv: *SpvModule, target: std.Target) !void {334fn writeMemoryModel(spv: *SpvModule) !void {
328 const gpa = spv.gpa;335 const addressing_model: spec.AddressingModel = blk: {
336 if (spv.hasFeature(.Shader)) {
337 break :blk switch (spv.target.cpu.arch) {
338 .spirv32 => .Logical, // TODO: I don't think this will ever be implemented.
339 .spirv64 => .PhysicalStorageBuffer64,
340 else => unreachable,
341 };
342 } else if (spv.hasFeature(.Kernel)) {
343 break :blk switch (spv.target.cpu.arch) {
344 .spirv32 => .Physical32,
345 .spirv64 => .Physical64,
346 else => unreachable,
347 };
348 }
329349
330 const addressing_model: spec.AddressingModel = switch (target.os.tag) {350 unreachable;
331 .opencl => switch (target.cpu.arch) {
332 .spirv32 => .Physical32,
333 .spirv64 => .Physical64,
334 else => unreachable,
335 },
336 .opengl, .vulkan => switch (target.cpu.arch) {
337 .spirv32 => .Logical, // TODO: I don't think this will ever be implemented.
338 .spirv64 => .PhysicalStorageBuffer64,
339 else => unreachable,
340 },
341 else => unreachable,
342 };351 };
343352 const memory_model: spec.MemoryModel = switch (spv.target.os.tag) {
344 const memory_model: spec.MemoryModel = switch (target.os.tag) {
345 .opencl => .OpenCL,353 .opencl => .OpenCL,
346 .opengl => .GLSL450,354 .vulkan, .opengl => .GLSL450,
347 .vulkan => .GLSL450,
348 else => unreachable,355 else => unreachable,
349 };356 };
350357 try spv.sections.memory_model.emit(spv.gpa, .OpMemoryModel, .{
351 try spv.sections.memory_model.emit(gpa, .OpMemoryModel, .{
352 .addressing_model = addressing_model,358 .addressing_model = addressing_model,
353 .memory_model = memory_model,359 .memory_model = memory_model,
354 });360 });