Commit 10678af876

Robin Voetter <robin@voetter.nl>
2021-05-16 13:09:32
SPIR-V: genBinOp setup
1 parent ae2e216
Changed files (1)
src
codegen
src/codegen/spirv.zig
@@ -74,6 +74,44 @@ pub const DeclGen = struct {
         OutOfMemory
     };
 
+    /// This structure is used to return information about a type typically used for arithmetic operations.
+    /// These types may either be integers, floats, or a vector of these. Most scalar operations also work on vectors,
+    /// so we can easily represent those as arithmetic types.
+    /// If the type is a scalar, 'inner type' refers to the scalar type. Otherwise, if its a vector, it refers
+    /// to the vector's element type.
+    const ArithmeticTypeInfo = struct {
+        /// A classification of the inner type.
+        const Class = enum {
+            /// A regular, **native**, integer operation.
+            /// This is only returned when the backend supports this int as a native type (when
+            /// the relevant capability is enabled).
+            integer,
+
+            /// A regular float. These are all required to be natively supported. Floating points for
+            /// which the relevant capability is not enabled are not emulated.
+            float,
+
+            /// An integer of a 'strange' size (which' bit size is not the same as its backing type. **Note**: this
+            /// may **also** include power-of-2 integers for which the relevant capability is not enabled), but still
+            /// within the limits of the largest natively supported integer type.
+            strange_integer,
+
+            /// An integer with more bits than the largest natively supported integer type.
+            composite_integer,
+        };
+
+        /// The number of bits in the inner type.
+        /// Note: this is the actual number of bits of the type, not the size of the backing integer.
+        bits: u32,
+
+        /// Whether the type is a vector.
+        is_vector: bool,
+
+        /// A classification of the inner type. These four scenarios
+        /// will all have to be handled slightly different.
+        class: Class,
+    };
+
     fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {
         @setCold(true);
         const src_loc = src.toSrcLocWithDecl(self.decl);
@@ -96,16 +134,14 @@ pub const DeclGen = struct {
     /// 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.
-    /// asserts `ty` is an integer.
     /// 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).
     /// TODO: Should the result of this function be cached?
-    fn backingIntBits(self: *DeclGen, ty: Type) ?u32 {
+    fn backingIntBits(self: *DeclGen, bits: u32) ?u32 {
         const target = self.module.getTarget();
-        const int_info = ty.intInfo(target);
 
         // TODO: Figure out what to do with u0/i0.
-        std.debug.assert(int_info.bits != 0);
+        std.debug.assert(bits != 0);
 
         // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.
         const ints = [_]struct{ bits: u32, feature: ?Target.spirv.Feature } {
@@ -121,7 +157,7 @@ pub const DeclGen = struct {
             else
                 true;
 
-            if (int_info.bits <= int.bits and has_feature) {
+            if (bits <= int.bits and has_feature) {
                 return int.bits;
             }
         }
@@ -150,6 +186,34 @@ pub const DeclGen = struct {
         return self.backingIntBits(ty) == null;
     }
 
+    fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo {
+        const target = self.module.getTarget();
+
+        return switch (ty.zigTypeTag()) {
+            .Float => ArithmeticTypeInfo{ .bits = ty.floatBits(target), .is_vector = false, .class = .float },
+            .Int => blk: {
+                const int_info = ty.intInfo(target);
+                // TODO: Maybe it's useful to also return this value.
+                const maybe_backing_bits = self.backingIntBits(int_info.bits);
+                break :blk ArithmeticTypeInfo{
+                    .bits = int_info.bits,
+                    .is_vector = false,
+                    .class = if (maybe_backing_bits) |backing_bits|
+                            if (backing_bits == int_info.bits)
+                                ArithmeticTypeInfo.Class.integer
+                            else
+                                ArithmeticTypeInfo.Class.strange_integer
+                        else
+                            .composite_integer
+                };
+            },
+            // As of yet, there is no vector support in the self-hosted compiler.
+            .Vector => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
+            // TODO: For which types is this the case?
+            else => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}),
+        };
+    }
+
     /// Generate a constant representing `val`.
     /// TODO: Deduplication?
     fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!u32 {
@@ -218,7 +282,8 @@ pub const DeclGen = struct {
             .Void => try writeInstruction(code, .OpTypeVoid, &[_]u32{ result_id }),
             .Bool => try writeInstruction(code, .OpTypeBool, &[_]u32{ result_id }),
             .Int => {
-                const backing_bits = self.backingIntBits(ty) orelse {
+                const int_info = ty.intInfo(target);
+                const backing_bits = self.backingIntBits(int_info.bits) orelse {
                     // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
                     return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement composite ints {}", .{ ty });
                 };
@@ -227,7 +292,10 @@ pub const DeclGen = struct {
                 try writeInstruction(code, .OpTypeInt, &[_]u32{
                     result_id,
                     backing_bits,
-                    @boolToInt(ty.isSignedInt()),
+                    switch (int_info.signedness) {
+                        .unsigned => 0,
+                        .signed => 1,
+                    },
                 });
             },
             .Float => {
@@ -346,6 +414,7 @@ pub const DeclGen = struct {
 
     fn genInst(self: *DeclGen, inst: *Inst) !?u32 {
         return switch (inst.tag) {
+            .add => try self.genBinOp(inst.castTag(.add).?),
             .arg => self.genArg(),
             // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them
             // throughout the IR.
@@ -358,6 +427,41 @@ pub const DeclGen = struct {
         };
     }
 
+    fn genBinOp(self: *DeclGen, inst: *Inst.BinOp) !u32 {
+        // TODO: Will lhs and rhs have the same type?
+        const lhs_id = try self.resolve(inst.lhs);
+        const rhs_id = try self.resolve(inst.rhs);
+
+        const binop_result_id = self.spv.allocResultId();
+        const result_type_id = try self.getOrGenType(inst.base.ty);
+
+        // TODO: Is the result the same as the argument types?
+        // This is supposed to be the case for SPIR-V.
+        std.debug.assert(inst.base.ty.eql(inst.lhs.ty) and inst.base.ty.eql(inst.rhs.ty));
+
+        // Binary operations are generally applicable to both scalar and vector operations in SPIR-V, but int and float
+        // versions of operations require different opcodes.
+        const info = try self.arithmeticTypeInfo(inst.base.ty);
+
+        if (info.class == .composite_integer)
+            return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: binary operations for composite integers", .{});
+
+        // Fetch the integer and float opcodes for each operation.
+        // Doing it this way removes a bit of code clutter.
+        const opcodes: [2]spec.Opcode = switch (inst.base.tag) {
+            .add => .{.OpIAdd, .OpFAdd},
+            else => unreachable,
+        };
+
+        const opcode = if (info.class == .float) opcodes[1] else opcodes[0];
+        try writeInstruction(&self.spv.fn_decls, opcode, &[_]u32{ result_type_id, binop_result_id, lhs_id, rhs_id });
+
+        if (info.class != .strange_integer)
+            return binop_result_id;
+
+        return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: strange integer operation mask", .{});
+    }
+
     fn genArg(self: *DeclGen) u32 {
         defer self.next_arg_index += 1;
         return self.args.items[self.next_arg_index];