| ... | ... | @@ -4,6 +4,8 @@ const Target = std.Target; |
| 4 | 4 | const log = std.log.scoped(.codegen); |
| 5 | 5 | |
| 6 | 6 | const spec = @import("spirv/spec.zig"); |
| 7 | const Opcode = spec.Opcode; |
| 8 | |
| 7 | 9 | const Module = @import("../Module.zig"); |
| 8 | 10 | const Decl = Module.Decl; |
| 9 | 11 | const Type = @import("../type.zig").Type; |
| ... | ... | @@ -15,12 +17,12 @@ const Inst = ir.Inst; |
| 15 | 17 | pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); |
| 16 | 18 | pub const ValueMap = std.AutoHashMap(*Inst, u32); |
| 17 | 19 | |
| 18 | | pub fn writeOpcode(code: *std.ArrayList(u32), opcode: spec.Opcode, arg_count: u32) !void { |
| 20 | pub fn writeOpcode(code: *std.ArrayList(u32), opcode: Opcode, arg_count: u32) !void { |
| 19 | 21 | const word_count = arg_count + 1; |
| 20 | 22 | try code.append((word_count << 16) | @enumToInt(opcode)); |
| 21 | 23 | } |
| 22 | 24 | |
| 23 | | pub fn writeInstruction(code: *std.ArrayList(u32), opcode: spec.Opcode, args: []const u32) !void { |
| 25 | pub fn writeInstruction(code: *std.ArrayList(u32), opcode: Opcode, args: []const u32) !void { |
| 24 | 26 | try writeOpcode(code, opcode, @intCast(u32, args.len)); |
| 25 | 27 | try code.appendSlice(args); |
| 26 | 28 | } |
| ... | ... | @@ -102,11 +104,14 @@ pub const DeclGen = struct { |
| 102 | 104 | |
| 103 | 105 | /// The number of bits in the inner type. |
| 104 | 106 | /// Note: this is the actual number of bits of the type, not the size of the backing integer. |
| 105 | | bits: u32, |
| 107 | bits: u16, |
| 106 | 108 | |
| 107 | 109 | /// Whether the type is a vector. |
| 108 | 110 | is_vector: bool, |
| 109 | 111 | |
| 112 | /// Whether the inner type is signed. Only relevant for integers. |
| 113 | signedness: std.builtin.Signedness, |
| 114 | |
| 110 | 115 | /// A classification of the inner type. These four scenarios |
| 111 | 116 | /// will all have to be handled slightly different. |
| 112 | 117 | class: Class, |
| ... | ... | @@ -137,14 +142,14 @@ pub const DeclGen = struct { |
| 137 | 142 | /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits). |
| 138 | 143 | /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers). |
| 139 | 144 | /// 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 { |
| 141 | 146 | const target = self.module.getTarget(); |
| 142 | 147 | |
| 143 | 148 | // TODO: Figure out what to do with u0/i0. |
| 144 | 149 | std.debug.assert(bits != 0); |
| 145 | 150 | |
| 146 | 151 | // 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 } { |
| 148 | 153 | .{ .bits = 8, .feature = .Int8 }, |
| 149 | 154 | .{ .bits = 16, .feature = .Int16 }, |
| 150 | 155 | .{ .bits = 32, .feature = null }, |
| ... | ... | @@ -171,7 +176,7 @@ pub const DeclGen = struct { |
| 171 | 176 | /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there |
| 172 | 177 | /// is no way of knowing whether those are actually supported. |
| 173 | 178 | /// TODO: Maybe this should be cached? |
| 174 | | fn largestSupportedIntBits(self: *DeclGen) u32 { |
| 179 | fn largestSupportedIntBits(self: *DeclGen) u16 { |
| 175 | 180 | const target = self.module.getTarget(); |
| 176 | 181 | return if (Target.spirv.featureSetHas(target.cpu.features, .Int64)) |
| 177 | 182 | 64 |
| ... | ... | @@ -190,7 +195,12 @@ pub const DeclGen = struct { |
| 190 | 195 | const target = self.module.getTarget(); |
| 191 | 196 | |
| 192 | 197 | 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 | }, |
| 194 | 204 | .Int => blk: { |
| 195 | 205 | const int_info = ty.intInfo(target); |
| 196 | 206 | // TODO: Maybe it's useful to also return this value. |
| ... | ... | @@ -198,6 +208,7 @@ pub const DeclGen = struct { |
| 198 | 208 | break :blk ArithmeticTypeInfo{ |
| 199 | 209 | .bits = int_info.bits, |
| 200 | 210 | .is_vector = false, |
| 211 | .signedness = int_info.signedness, |
| 201 | 212 | .class = if (maybe_backing_bits) |backing_bits| |
| 202 | 213 | if (backing_bits == int_info.bits) |
| 203 | 214 | ArithmeticTypeInfo.Class.integer |
| ... | ... | @@ -228,7 +239,7 @@ pub const DeclGen = struct { |
| 228 | 239 | |
| 229 | 240 | switch (ty.zigTypeTag()) { |
| 230 | 241 | .Bool => { |
| 231 | | const opcode: spec.Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse; |
| 242 | const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse; |
| 232 | 243 | try writeInstruction(code, opcode, &[_]u32{ result_type_id, result_id }); |
| 233 | 244 | }, |
| 234 | 245 | .Float => { |
| ... | ... | @@ -414,7 +425,10 @@ pub const DeclGen = struct { |
| 414 | 425 | |
| 415 | 426 | fn genInst(self: *DeclGen, inst: *Inst) !?u32 { |
| 416 | 427 | 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).?), |
| 418 | 432 | .arg => self.genArg(), |
| 419 | 433 | // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them |
| 420 | 434 | // throughout the IR. |
| ... | ... | @@ -446,16 +460,27 @@ pub const DeclGen = struct { |
| 446 | 460 | if (info.class == .composite_integer) |
| 447 | 461 | return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: binary operations for composite integers", .{}); |
| 448 | 462 | |
| 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 | |
| 453 | 476 | else => unreachable, |
| 454 | 477 | }; |
| 455 | 478 | |
| 456 | | const opcode = if (info.class == .float) opcodes[1] else opcodes[0]; |
| 457 | 479 | try writeInstruction(&self.spv.fn_decls, opcode, &[_]u32{ result_type_id, binop_result_id, lhs_id, rhs_id }); |
| 458 | 480 | |
| 481 | // TODO: Trap on overflow? Probably going to be annoying. |
| 482 | // TODO: Look into NoSignedWrap/NoUnsignedWrap extensions. |
| 483 | |
| 459 | 484 | if (info.class != .strange_integer) |
| 460 | 485 | return binop_result_id; |
| 461 | 486 | |