Commit 6146abee1e

Robin Voetter <robin@voetter.nl>
2022-11-26 23:42:04
spirv: add_with_overflow
Implements lowering for the add_with_overflow AIR instructions. Also implements a helper function, simpleStructType, to quickly generate a SPIR-V structure type without having to do the whole allocation dance.
1 parent c23d668
Changed files (2)
src
codegen
src/codegen/spirv/type.zig
@@ -436,17 +436,17 @@ pub const Type = extern union {
             base: Payload = .{ .tag = .@"struct" },
             // TODO: name
             members: []Member,
-            decorations: StructDecorations,
+            decorations: StructDecorations = .{},
 
             /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
             /// order of the `members` slice and `MemberDecorations` struct.
-            member_decoration_extra: []u32,
+            member_decoration_extra: []u32 = &.{},
 
             pub const Member = struct {
                 ty: Ref,
                 offset: u32,
                 // TODO: name
-                decorations: MemberDecorations,
+                decorations: MemberDecorations = .{},
             };
 
             pub const StructDecorations = packed struct {
src/codegen/spirv.zig
@@ -459,6 +459,22 @@ pub const DeclGen = struct {
         return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth());
     }
 
+    /// Construct a simple struct type which consists of some members, and no decorations.
+    /// `members` lifetime only needs to last for this function as it is copied.
+    fn simpleStructType(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !SpvType.Ref {
+        const payload = try self.spv.arena.create(SpvType.Payload.Struct);
+        payload.* = .{
+            .members = try self.spv.arena.dupe(SpvType.Payload.Struct.Member, members),
+            .decorations = .{},
+        };
+        return try self.spv.resolveType(SpvType.initPayload(&payload.base));
+    }
+
+    fn simpleStructTypeId(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !IdResultType {
+        const type_ref = try self.simpleStructType(members);
+        return self.spv.typeResultId(type_ref);
+    }
+
     /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
     fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref {
         const target = self.getTarget();
@@ -555,25 +571,10 @@ pub const DeclGen = struct {
                 const len_align = len_ty.abiAlignment(target);
                 const len_offset = std.mem.alignForwardGeneric(u64, ptr_size, len_align);
 
-                const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, 2);
-                members[0] = .{
-                    .ty = spv_ptr_ty,
-                    .offset = 0,
-                    .decorations = .{},
-                };
-                members[1] = .{
-                    .ty = try self.sizeType(),
-                    .offset = @intCast(u32, len_offset),
-                    .decorations = .{},
-                };
-
-                const slice_payload = try self.spv.arena.create(SpvType.Payload.Struct);
-                slice_payload.* = .{
-                    .members = members,
-                    .decorations = .{},
-                    .member_decoration_extra = &.{},
-                };
-                return try self.spv.resolveType(SpvType.initPayload(&slice_payload.base));
+                return try self.simpleStructType(&.{
+                    .{ .ty = spv_ptr_ty, .offset = 0 },
+                    .{ .ty = try self.sizeType(), .offset = @intCast(u32, len_offset) },
+                });
             },
             .Vector => {
                 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
@@ -594,7 +595,24 @@ pub const DeclGen = struct {
             },
             .Struct => {
                 if (ty.isSimpleTupleOrAnonStruct()) {
-                    return self.todo("implement tuple struct type", .{});
+                    const tuple = ty.tupleFields();
+                    const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, tuple.types.len);
+                    var member_index: usize = 0;
+                    for (tuple.types) |field_ty, i| {
+                        const field_val = tuple.values[i];
+                        if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue;
+
+                        members[member_index] = .{
+                            .ty = try self.resolveType(field_ty),
+                            .offset = 0,
+                        };
+                    }
+
+                    const payload = try self.spv.arena.create(SpvType.Payload.Struct);
+                    payload.* = .{
+                        .members = members[0..member_index],
+                    };
+                    return try self.spv.resolveType(SpvType.initPayload(&payload.base));
                 }
 
                 const struct_ty = ty.castTag(.@"struct").?.data;
@@ -611,15 +629,12 @@ pub const DeclGen = struct {
                     members[member_index] = .{
                         .ty = try self.resolveType(field.ty),
                         .offset = field.offset,
-                        .decorations = .{},
                     };
                 }
 
                 const payload = try self.spv.arena.create(SpvType.Payload.Struct);
                 payload.* = .{
                     .members = members[0..member_index],
-                    .decorations = .{},
-                    .member_decoration_extra = &.{},
                 };
                 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
             },
@@ -709,6 +724,8 @@ pub const DeclGen = struct {
             .sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub),
             .mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul),
 
+            .add_with_overflow => try self.airOverflowArithOp(inst),
+
             .shuffle => try self.airShuffle(inst),
 
             .bit_and  => try self.airBinOpSimple(inst, .OpBitwiseAnd),
@@ -719,6 +736,7 @@ pub const DeclGen = struct {
 
             .bitcast        => try self.airBitcast(inst),
             .not            => try self.airNot(inst),
+
             .slice_ptr      => try self.airSliceField(inst, 0),
             .slice_len      => try self.airSliceField(inst, 1),
             .slice_elem_ptr => try self.airSliceElemPtr(inst),
@@ -841,6 +859,82 @@ pub const DeclGen = struct {
         return result_id.toRef();
     }
 
+    fn airOverflowArithOp(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
+        if (self.liveness.isUnused(inst)) return null;
+
+        const target = self.getTarget();
+
+        const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+        const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
+        const lhs = try self.resolve(extra.lhs);
+        const rhs = try self.resolve(extra.rhs);
+
+        const operand_ty = self.air.typeOf(extra.lhs);
+        const result_ty = self.air.typeOfIndex(inst);
+
+        const operand_ty_id = try self.resolveTypeId(operand_ty);
+        const result_type_id = try self.resolveTypeId(result_ty);
+
+        const operand_bits = operand_ty.intInfo(target).bits;
+        const overflow_member_ty = try self.intType(.unsigned, operand_bits);
+        const overflow_member_ty_id = self.spv.typeResultId(overflow_member_ty);
+
+        const op_result_id = blk: {
+            // Construct the SPIR-V result type.
+            // It is almost the same as the zig one, except that the fields must be the same type
+            // and they must be unsigned.
+            const overflow_result_ty = try self.simpleStructTypeId(&.{
+                .{ .ty = overflow_member_ty, .offset = 0 },
+                .{ .ty = overflow_member_ty, .offset = @intCast(u32, operand_ty.abiSize(target)) },
+            });
+            const result_id = self.spv.allocId();
+            try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{
+                .id_result_type = overflow_result_ty,
+                .id_result = result_id,
+                .operand_1 = lhs,
+                .operand_2 = rhs,
+            });
+            break :blk result_id.toRef();
+        };
+
+        // Now convert the SPIR-V flavor result into a Zig-flavor result.
+        // First, extract the two fields.
+        const unsigned_result = try self.extractField(overflow_member_ty_id, op_result_id, 0);
+        const overflow = try self.extractField(overflow_member_ty_id, op_result_id, 0);
+
+        // We need to convert the results to the types that Zig expects here.
+        // The `result` is the same type except unsigned, so we can just bitcast that.
+        const result = try self.bitcast(operand_ty_id, unsigned_result);
+
+        // The overflow needs to be converted into whatever is used to represent it in Zig.
+        const casted_overflow = blk: {
+            const ov_ty = result_ty.tupleFields().types[1];
+            const ov_ty_id = try self.resolveTypeId(ov_ty);
+            const result_id = self.spv.allocId();
+            try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
+                .id_result_type = ov_ty_id,
+                .id_result = result_id,
+                .unsigned_value = overflow,
+            });
+            break :blk result_id.toRef();
+        };
+
+        // TODO: If copying this function for borrow, make sure to convert -1 to 1 as appropriate.
+
+        // Finally, construct the Zig type.
+        // Layout is result, overflow.
+        const result_id = self.spv.allocId();
+        try self.func.body.emit(self.spv.gpa, .OpCompositeConstruct, .{
+            .id_result_type = result_type_id,
+            .id_result = result_id,
+            .constituents = &.{
+                result,
+                casted_overflow,
+            },
+        });
+        return result_id.toRef();
+    }
+
     fn airShuffle(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
         if (self.liveness.isUnused(inst)) return null;
         const ty = self.air.typeOfIndex(inst);
@@ -923,20 +1017,24 @@ pub const DeclGen = struct {
         return result_id.toRef();
     }
 
-    fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
-        if (self.liveness.isUnused(inst)) return null;
-        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
-        const operand_id = try self.resolve(ty_op.operand);
+    fn bitcast(self: *DeclGen, target_type_id: IdResultType, value_id: IdRef) !IdRef {
         const result_id = self.spv.allocId();
-        const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst));
         try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
-            .id_result_type = result_type_id,
+            .id_result_type = target_type_id,
             .id_result = result_id,
-            .operand = operand_id,
+            .operand = value_id,
         });
         return result_id.toRef();
     }
 
+    fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
+        if (self.liveness.isUnused(inst)) return null;
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        const operand_id = try self.resolve(ty_op.operand);
+        const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst));
+        return try self.bitcast(result_type_id, operand_id);
+    }
+
     fn airNot(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
         if (self.liveness.isUnused(inst)) return null;
         const ty_op = self.air.instructions.items(.data)[inst].ty_op;
@@ -951,19 +1049,27 @@ pub const DeclGen = struct {
         return result_id.toRef();
     }
 
-    fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef {
-        if (self.liveness.isUnused(inst)) return null;
-        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+    fn extractField(self: *DeclGen, result_ty: IdResultType, object: IdRef, field: u32) !IdRef {
         const result_id = self.spv.allocId();
         try self.func.body.emit(self.spv.gpa, .OpCompositeExtract, .{
-            .id_result_type = try self.resolveTypeId(self.air.typeOfIndex(inst)),
+            .id_result_type = result_ty,
             .id_result = result_id,
-            .composite = try self.resolve(ty_op.operand),
+            .composite = object,
             .indexes = &.{field},
         });
         return result_id.toRef();
     }
 
+    fn airSliceField(self: *DeclGen, inst: Air.Inst.Index, field: u32) !?IdRef {
+        if (self.liveness.isUnused(inst)) return null;
+        const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+        return try self.extractField(
+            try self.resolveTypeId(self.air.typeOfIndex(inst)),
+            try self.resolve(ty_op.operand),
+            field,
+        );
+    }
+
     fn airSliceElemPtr(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
         const bin_op = self.air.instructions.items(.data)[inst].bin_op;
         const slice_ty = self.air.typeOf(bin_op.lhs);