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