Commit 458c338aeb

Robin Voetter <robin@voetter.nl>
2021-05-15 01:38:39
SPIR-V: Compute backing integer bits
1 parent de6df2b
Changed files (1)
src
codegen
src/codegen/spirv.zig
@@ -2,6 +2,8 @@ const std = @import("std");
 const Allocator = std.mem.Allocator;
 const log = std.log.scoped(.codegen);
 
+const Target = std.Target;
+
 const spec = @import("spirv/spec.zig");
 const Module = @import("../Module.zig");
 const Decl = Module.Decl;
@@ -63,6 +65,43 @@ pub const DeclGen = struct {
         return error.AnalysisFail;
     }
 
+    /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
+    /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign
+    /// included), the width of the underlying type which represents it, given the enabled features for the current target.
+    /// If the result is `null`, the largest type the target platform supports natively is not able to perform computations using
+    /// that size. In this case, multiple elements of the largest type should be used.
+    /// The backing type will be chosen as the smallest supported integer larger or equal to it in number of bits.
+    /// The result is valid to be used with OpTypeInt.
+    /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
+    /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
+    fn backingIntBits(self: *DeclGen, bits: u32) ?u32 {
+        // TODO: Figure out what to do with u0/i0.
+        std.debug.assert(bits != 0);
+
+        const target = self.module.getTarget();
+
+        // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.
+        const ints = [_]struct{ bits: u32, feature: ?Target.spirv.Feature } {
+            .{ .bits = 8, .feature = .Int8 },
+            .{ .bits = 16, .feature = .Int16 },
+            .{ .bits = 32, .feature = null },
+            .{ .bits = 64, .feature = .Int64 },
+        };
+
+        for (ints) |int| {
+            const has_feature = if (int.feature) |feature|
+                Target.spirv.featureSetHas(target.cpu.features, feature)
+            else
+                true;
+
+            if (bits <= int.bits and has_feature) {
+                return int.bits;
+            }
+        }
+
+        return null;
+    }
+
     pub fn getOrGenType(self: *DeclGen, t: Type) !u32 {
         // We can't use getOrPut here so we can recursively generate types.
         if (self.types.get(t)) |already_generated| {
@@ -76,10 +115,12 @@ pub const DeclGen = struct {
             .Bool => try writeInstruction(&self.spv.types_and_globals, .OpTypeBool, &[_]u32{ result }),
             .Int => {
                 const int_info = t.intInfo(self.module.getTarget());
-                // TODO: Capabilities.
+                const backing_bits = self.backingIntBits(int_info.bits) orelse
+                    return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement fallback for integer of {} bits", .{ int_info.bits });
+
                 try writeInstruction(&self.spv.types_and_globals, .OpTypeInt, &[_]u32{
                     result,
-                    int_info.bits,
+                    backing_bits,
                     switch (int_info.signedness) {
                         .unsigned => 0,
                         .signed => 1,