authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-16 13:32:32+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-16 14:20:12+02:00
log4735e95d1699c90e821655bdbe0afbb1044738ea
tree8eb7dbbc764378fca05c8babc4d72742dda6495d
parent10678af8768a8d8cad7640837d0ec354dc8c07bc

SPIR-V: More binary operations


1 files changed, 39 insertions(+), 14 deletions(-)

src/codegen/spirv.zig+39-14
......@@ -4,6 +4,8 @@ const Target = std.Target;
44const log = std.log.scoped(.codegen);
55
66const spec = @import("spirv/spec.zig");
7const Opcode = spec.Opcode;
8
79const Module = @import("../Module.zig");
810const Decl = Module.Decl;
911const Type = @import("../type.zig").Type;
......@@ -15,12 +17,12 @@ const Inst = ir.Inst;
1517pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
1618pub const ValueMap = std.AutoHashMap(*Inst, u32);
1719
18pub fn writeOpcode(code: *std.ArrayList(u32), opcode: spec.Opcode, arg_count: u32) !void {
20pub fn writeOpcode(code: *std.ArrayList(u32), opcode: Opcode, arg_count: u32) !void {
1921 const word_count = arg_count + 1;
2022 try code.append((word_count << 16) | @enumToInt(opcode));
2123}
2224
23pub fn writeInstruction(code: *std.ArrayList(u32), opcode: spec.Opcode, args: []const u32) !void {
25pub fn writeInstruction(code: *std.ArrayList(u32), opcode: Opcode, args: []const u32) !void {
2426 try writeOpcode(code, opcode, @intCast(u32, args.len));
2527 try code.appendSlice(args);
2628}
......@@ -102,11 +104,14 @@ pub const DeclGen = struct {
102104
103105 /// The number of bits in the inner type.
104106 /// Note: this is the actual number of bits of the type, not the size of the backing integer.
105 bits: u32,
107 bits: u16,
106108
107109 /// Whether the type is a vector.
108110 is_vector: bool,
109111
112 /// Whether the inner type is signed. Only relevant for integers.
113 signedness: std.builtin.Signedness,
114
110115 /// A classification of the inner type. These four scenarios
111116 /// will all have to be handled slightly different.
112117 class: Class,
......@@ -137,14 +142,14 @@ pub const DeclGen = struct {
137142 /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
138143 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
139144 /// TODO: Should the result of this function be cached?
140 fn backingIntBits(self: *DeclGen, bits: u32) ?u32 {
145 fn backingIntBits(self: *DeclGen, bits: u16) ?u16 {
141146 const target = self.module.getTarget();
142147
143148 // TODO: Figure out what to do with u0/i0.
144149 std.debug.assert(bits != 0);
145150
146151 // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.
147 const ints = [_]struct{ bits: u32, feature: ?Target.spirv.Feature } {
152 const ints = [_]struct{ bits: u16, feature: ?Target.spirv.Feature } {
148153 .{ .bits = 8, .feature = .Int8 },
149154 .{ .bits = 16, .feature = .Int16 },
150155 .{ .bits = 32, .feature = null },
......@@ -171,7 +176,7 @@ pub const DeclGen = struct {
171176 /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there
172177 /// is no way of knowing whether those are actually supported.
173178 /// TODO: Maybe this should be cached?
174 fn largestSupportedIntBits(self: *DeclGen) u32 {
179 fn largestSupportedIntBits(self: *DeclGen) u16 {
175180 const target = self.module.getTarget();
176181 return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
177182 64
......@@ -190,7 +195,12 @@ pub const DeclGen = struct {
190195 const target = self.module.getTarget();
191196
192197 return switch (ty.zigTypeTag()) {
193 .Float => ArithmeticTypeInfo{ .bits = ty.floatBits(target), .is_vector = false, .class = .float },
198 .Float => ArithmeticTypeInfo{
199 .bits = ty.floatBits(target),
200 .is_vector = false,
201 .signedness = .signed, // I guess technically it is.
202 .class = .float
203 },
194204 .Int => blk: {
195205 const int_info = ty.intInfo(target);
196206 // TODO: Maybe it's useful to also return this value.
......@@ -198,6 +208,7 @@ pub const DeclGen = struct {
198208 break :blk ArithmeticTypeInfo{
199209 .bits = int_info.bits,
200210 .is_vector = false,
211 .signedness = int_info.signedness,
201212 .class = if (maybe_backing_bits) |backing_bits|
202213 if (backing_bits == int_info.bits)
203214 ArithmeticTypeInfo.Class.integer
......@@ -228,7 +239,7 @@ pub const DeclGen = struct {
228239
229240 switch (ty.zigTypeTag()) {
230241 .Bool => {
231 const opcode: spec.Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;
242 const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;
232243 try writeInstruction(code, opcode, &[_]u32{ result_type_id, result_id });
233244 },
234245 .Float => {
......@@ -414,7 +425,10 @@ pub const DeclGen = struct {
414425
415426 fn genInst(self: *DeclGen, inst: *Inst) !?u32 {
416427 return switch (inst.tag) {
417 .add => try self.genBinOp(inst.castTag(.add).?),
428 .add, .addwrap => try self.genBinOp(inst.castTag(.add).?),
429 .sub, .subwrap => try self.genBinOp(inst.castTag(.sub).?),
430 .mul, .mulwrap => try self.genBinOp(inst.castTag(.mul).?),
431 .div => try self.genBinOp(inst.castTag(.div).?),
418432 .arg => self.genArg(),
419433 // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them
420434 // throughout the IR.
......@@ -446,16 +460,27 @@ pub const DeclGen = struct {
446460 if (info.class == .composite_integer)
447461 return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: binary operations for composite integers", .{});
448462
449 // Fetch the integer and float opcodes for each operation.
450 // Doing it this way removes a bit of code clutter.
451 const opcodes: [2]spec.Opcode = switch (inst.base.tag) {
452 .add => .{.OpIAdd, .OpFAdd},
463 const is_float = info.class == .float;
464 const is_signed = info.signedness == .signed;
465 // **Note**: All these operations must be valid for vectors of floats and integers as well!
466 const opcode = switch (inst.base.tag) {
467 // The regular integer operations are all defined for wrapping. Since theyre only relevant for integers,
468 // we can just switch on both cases here.
469 .add, .addwrap => if (is_float) Opcode.OpFAdd else Opcode.OpIAdd,
470 .sub, .subwrap => if (is_float) Opcode.OpFSub else Opcode.OpISub,
471 .mul, .mulwrap => if (is_float) Opcode.OpFMul else Opcode.OpIMul,
472 // TODO: Trap if divisor is 0?
473 // TODO: Figure out of OpSDiv for unsigned/OpUDiv for signed does anything useful.
474 .div => if (is_float) Opcode.OpFDiv else if (is_signed) Opcode.OpSDiv else Opcode.OpUDiv,
475
453476 else => unreachable,
454477 };
455478
456 const opcode = if (info.class == .float) opcodes[1] else opcodes[0];
457479 try writeInstruction(&self.spv.fn_decls, opcode, &[_]u32{ result_type_id, binop_result_id, lhs_id, rhs_id });
458480
481 // TODO: Trap on overflow? Probably going to be annoying.
482 // TODO: Look into NoSignedWrap/NoUnsignedWrap extensions.
483
459484 if (info.class != .strange_integer)
460485 return binop_result_id;
461486