Commit 10678af876
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];