| ... | ... | @@ -1,44 +1,50 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const Allocator = std.mem.Allocator; |
| 3 | const Target = std.Target; |
| 3 | 4 | const log = std.log.scoped(.codegen); |
| 4 | 5 | |
| 5 | 6 | const spec = @import("spirv/spec.zig"); |
| 7 | const Opcode = spec.Opcode; |
| 8 | |
| 6 | 9 | const Module = @import("../Module.zig"); |
| 7 | 10 | const Decl = Module.Decl; |
| 8 | 11 | const Type = @import("../type.zig").Type; |
| 12 | const Value = @import("../value.zig").Value; |
| 13 | const LazySrcLoc = Module.LazySrcLoc; |
| 14 | const ir = @import("../ir.zig"); |
| 15 | const Inst = ir.Inst; |
| 9 | 16 | |
| 10 | 17 | pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); |
| 18 | pub const ValueMap = std.AutoHashMap(*Inst, u32); |
| 19 | |
| 20 | pub fn writeOpcode(code: *std.ArrayList(u32), opcode: Opcode, arg_count: u32) !void { |
| 21 | const word_count = arg_count + 1; |
| 22 | try code.append((word_count << 16) | @enumToInt(opcode)); |
| 23 | } |
| 11 | 24 | |
| 12 | | pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void { |
| 13 | | const word_count = @intCast(u32, args.len + 1); |
| 14 | | try code.append((word_count << 16) | @enumToInt(instr)); |
| 25 | pub fn writeInstruction(code: *std.ArrayList(u32), opcode: Opcode, args: []const u32) !void { |
| 26 | try writeOpcode(code, opcode, @intCast(u32, args.len)); |
| 15 | 27 | try code.appendSlice(args); |
| 16 | 28 | } |
| 17 | 29 | |
| 30 | /// This structure represents a SPIR-V binary module being compiled, and keeps track of relevant information |
| 31 | /// such as code for the different logical sections, and the next result-id. |
| 18 | 32 | pub const SPIRVModule = struct { |
| 19 | | next_result_id: u32 = 0, |
| 20 | | |
| 21 | | target: std.Target, |
| 22 | | |
| 23 | | types: TypeMap, |
| 24 | | |
| 25 | | types_and_globals: std.ArrayList(u32), |
| 33 | next_result_id: u32, |
| 34 | types_globals_constants: std.ArrayList(u32), |
| 26 | 35 | fn_decls: std.ArrayList(u32), |
| 27 | 36 | |
| 28 | | pub fn init(target: std.Target, allocator: *Allocator) SPIRVModule { |
| 37 | pub fn init(allocator: *Allocator) SPIRVModule { |
| 29 | 38 | return .{ |
| 30 | | .target = target, |
| 31 | | .types = TypeMap.init(allocator), |
| 32 | | .types_and_globals = std.ArrayList(u32).init(allocator), |
| 39 | .next_result_id = 1, // 0 is an invalid SPIR-V result ID. |
| 40 | .types_globals_constants = std.ArrayList(u32).init(allocator), |
| 33 | 41 | .fn_decls = std.ArrayList(u32).init(allocator), |
| 34 | 42 | }; |
| 35 | 43 | } |
| 36 | 44 | |
| 37 | 45 | pub fn deinit(self: *SPIRVModule) void { |
| 46 | self.types_globals_constants.deinit(); |
| 38 | 47 | self.fn_decls.deinit(); |
| 39 | | self.types_and_globals.deinit(); |
| 40 | | self.types.deinit(); |
| 41 | | self.* = undefined; |
| 42 | 48 | } |
| 43 | 49 | |
| 44 | 50 | pub fn allocResultId(self: *SPIRVModule) u32 { |
| ... | ... | @@ -49,31 +55,326 @@ pub const SPIRVModule = struct { |
| 49 | 55 | pub fn resultIdBound(self: *SPIRVModule) u32 { |
| 50 | 56 | return self.next_result_id; |
| 51 | 57 | } |
| 58 | }; |
| 59 | |
| 60 | /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that. |
| 61 | pub const DeclGen = struct { |
| 62 | module: *Module, |
| 63 | spv: *SPIRVModule, |
| 64 | |
| 65 | args: std.ArrayList(u32), |
| 66 | next_arg_index: u32, |
| 67 | |
| 68 | types: TypeMap, |
| 69 | values: ValueMap, |
| 70 | |
| 71 | decl: *Decl, |
| 72 | error_msg: ?*Module.ErrorMsg, |
| 73 | |
| 74 | const Error = error{ |
| 75 | AnalysisFail, |
| 76 | OutOfMemory |
| 77 | }; |
| 78 | |
| 79 | /// This structure is used to return information about a type typically used for arithmetic operations. |
| 80 | /// These types may either be integers, floats, or a vector of these. Most scalar operations also work on vectors, |
| 81 | /// so we can easily represent those as arithmetic types. |
| 82 | /// If the type is a scalar, 'inner type' refers to the scalar type. Otherwise, if its a vector, it refers |
| 83 | /// to the vector's element type. |
| 84 | const ArithmeticTypeInfo = struct { |
| 85 | /// A classification of the inner type. |
| 86 | const Class = enum { |
| 87 | /// A boolean. |
| 88 | bool, |
| 52 | 89 | |
| 53 | | pub fn getOrGenType(self: *SPIRVModule, t: Type) !u32 { |
| 90 | /// A regular, **native**, integer. |
| 91 | /// This is only returned when the backend supports this int as a native type (when |
| 92 | /// the relevant capability is enabled). |
| 93 | integer, |
| 94 | |
| 95 | /// A regular float. These are all required to be natively supported. Floating points for |
| 96 | /// which the relevant capability is not enabled are not emulated. |
| 97 | float, |
| 98 | |
| 99 | /// An integer of a 'strange' size (which' bit size is not the same as its backing type. **Note**: this |
| 100 | /// may **also** include power-of-2 integers for which the relevant capability is not enabled), but still |
| 101 | /// within the limits of the largest natively supported integer type. |
| 102 | strange_integer, |
| 103 | |
| 104 | /// An integer with more bits than the largest natively supported integer type. |
| 105 | composite_integer, |
| 106 | }; |
| 107 | |
| 108 | /// The number of bits in the inner type. |
| 109 | /// Note: this is the actual number of bits of the type, not the size of the backing integer. |
| 110 | bits: u16, |
| 111 | |
| 112 | /// Whether the type is a vector. |
| 113 | is_vector: bool, |
| 114 | |
| 115 | /// Whether the inner type is signed. Only relevant for integers. |
| 116 | signedness: std.builtin.Signedness, |
| 117 | |
| 118 | /// A classification of the inner type. These scenarios |
| 119 | /// will all have to be handled slightly different. |
| 120 | class: Class, |
| 121 | }; |
| 122 | |
| 123 | fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error { |
| 124 | @setCold(true); |
| 125 | const src_loc = src.toSrcLocWithDecl(self.decl); |
| 126 | self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, format, args); |
| 127 | return error.AnalysisFail; |
| 128 | } |
| 129 | |
| 130 | fn resolve(self: *DeclGen, inst: *Inst) !u32 { |
| 131 | if (inst.value()) |val| { |
| 132 | return self.genConstant(inst.ty, val); |
| 133 | } |
| 134 | |
| 135 | return self.values.get(inst).?; // Instruction does not dominate all uses! |
| 136 | } |
| 137 | |
| 138 | /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need |
| 139 | /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign |
| 140 | /// included), the width of the underlying type which represents it, given the enabled features for the current target. |
| 141 | /// If the result is `null`, the largest type the target platform supports natively is not able to perform computations using |
| 142 | /// that size. In this case, multiple elements of the largest type should be used. |
| 143 | /// The backing type will be chosen as the smallest supported integer larger or equal to it in number of bits. |
| 144 | /// The result is valid to be used with OpTypeInt. |
| 145 | /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits). |
| 146 | /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers). |
| 147 | /// TODO: Should the result of this function be cached? |
| 148 | fn backingIntBits(self: *DeclGen, bits: u16) ?u16 { |
| 149 | const target = self.module.getTarget(); |
| 150 | |
| 151 | // TODO: Figure out what to do with u0/i0. |
| 152 | std.debug.assert(bits != 0); |
| 153 | |
| 154 | // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively. |
| 155 | // 32-bit integers are always supported (see spec, 2.16.1, Data rules). |
| 156 | const ints = [_]struct{ bits: u16, feature: ?Target.spirv.Feature } { |
| 157 | .{ .bits = 8, .feature = .Int8 }, |
| 158 | .{ .bits = 16, .feature = .Int16 }, |
| 159 | .{ .bits = 32, .feature = null }, |
| 160 | .{ .bits = 64, .feature = .Int64 }, |
| 161 | }; |
| 162 | |
| 163 | for (ints) |int| { |
| 164 | const has_feature = if (int.feature) |feature| |
| 165 | Target.spirv.featureSetHas(target.cpu.features, feature) |
| 166 | else |
| 167 | true; |
| 168 | |
| 169 | if (bits <= int.bits and has_feature) { |
| 170 | return int.bits; |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | return null; |
| 175 | } |
| 176 | |
| 177 | /// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if |
| 178 | /// the Int64 capability is enabled). |
| 179 | /// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits). |
| 180 | /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there |
| 181 | /// is no way of knowing whether those are actually supported. |
| 182 | /// TODO: Maybe this should be cached? |
| 183 | fn largestSupportedIntBits(self: *DeclGen) u16 { |
| 184 | const target = self.module.getTarget(); |
| 185 | return if (Target.spirv.featureSetHas(target.cpu.features, .Int64)) |
| 186 | 64 |
| 187 | else |
| 188 | 32; |
| 189 | } |
| 190 | |
| 191 | /// Checks whether the type is "composite int", an integer consisting of multiple native integers. These are represented by |
| 192 | /// arrays of largestSupportedIntBits(). |
| 193 | /// Asserts `ty` is an integer. |
| 194 | fn isCompositeInt(self: *DeclGen, ty: Type) bool { |
| 195 | return self.backingIntBits(ty) == null; |
| 196 | } |
| 197 | |
| 198 | fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo { |
| 199 | const target = self.module.getTarget(); |
| 200 | |
| 201 | return switch (ty.zigTypeTag()) { |
| 202 | .Bool => ArithmeticTypeInfo{ |
| 203 | .bits = 1, // Doesn't matter for this class. |
| 204 | .is_vector = false, |
| 205 | .signedness = .unsigned, // Technically, but doesn't matter for this class. |
| 206 | .class = .bool, |
| 207 | }, |
| 208 | .Float => ArithmeticTypeInfo{ |
| 209 | .bits = ty.floatBits(target), |
| 210 | .is_vector = false, |
| 211 | .signedness = .signed, // Technically, but doesn't matter for this class. |
| 212 | .class = .float, |
| 213 | }, |
| 214 | .Int => blk: { |
| 215 | const int_info = ty.intInfo(target); |
| 216 | // TODO: Maybe it's useful to also return this value. |
| 217 | const maybe_backing_bits = self.backingIntBits(int_info.bits); |
| 218 | break :blk ArithmeticTypeInfo{ |
| 219 | .bits = int_info.bits, |
| 220 | .is_vector = false, |
| 221 | .signedness = int_info.signedness, |
| 222 | .class = if (maybe_backing_bits) |backing_bits| |
| 223 | if (backing_bits == int_info.bits) |
| 224 | ArithmeticTypeInfo.Class.integer |
| 225 | else |
| 226 | ArithmeticTypeInfo.Class.strange_integer |
| 227 | else |
| 228 | .composite_integer |
| 229 | }; |
| 230 | }, |
| 231 | // As of yet, there is no vector support in the self-hosted compiler. |
| 232 | .Vector => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}), |
| 233 | // TODO: For which types is this the case? |
| 234 | else => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}), |
| 235 | }; |
| 236 | } |
| 237 | |
| 238 | /// Generate a constant representing `val`. |
| 239 | /// TODO: Deduplication? |
| 240 | fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!u32 { |
| 241 | const code = &self.spv.types_globals_constants; |
| 242 | const result_id = self.spv.allocResultId(); |
| 243 | const result_type_id = try self.getOrGenType(ty); |
| 244 | |
| 245 | if (val.isUndef()) { |
| 246 | try writeInstruction(code, .OpUndef, &[_]u32{ result_type_id, result_id }); |
| 247 | return result_id; |
| 248 | } |
| 249 | |
| 250 | switch (ty.zigTypeTag()) { |
| 251 | .Bool => { |
| 252 | const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse; |
| 253 | try writeInstruction(code, opcode, &[_]u32{ result_type_id, result_id }); |
| 254 | }, |
| 255 | .Float => { |
| 256 | // At this point we are guaranteed that the target floating point type is supported, otherwise the function |
| 257 | // would have exited at getOrGenType(ty). |
| 258 | |
| 259 | // f16 and f32 require one word of storage. f64 requires 2, low-order first. |
| 260 | |
| 261 | switch (val.tag()) { |
| 262 | .float_16 => try writeInstruction(code, .OpConstant, &[_]u32{ |
| 263 | result_type_id, |
| 264 | result_id, |
| 265 | @bitCast(u16, val.castTag(.float_16).?.data) |
| 266 | }), |
| 267 | .float_32 => try writeInstruction(code, .OpConstant, &[_]u32{ |
| 268 | result_type_id, |
| 269 | result_id, |
| 270 | @bitCast(u32, val.castTag(.float_32).?.data) |
| 271 | }), |
| 272 | .float_64 => { |
| 273 | const float_bits = @bitCast(u64, val.castTag(.float_64).?.data); |
| 274 | try writeInstruction(code, .OpConstant, &[_]u32{ |
| 275 | result_type_id, |
| 276 | result_id, |
| 277 | @truncate(u32, float_bits), |
| 278 | @truncate(u32, float_bits >> 32), |
| 279 | }); |
| 280 | }, |
| 281 | .float_128 => unreachable, // Filtered out in the call to getOrGenType. |
| 282 | // TODO: What tags do we need to handle here anyway? |
| 283 | else => return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: float constant generation of value {s}\n", .{ val.tag() }), |
| 284 | } |
| 285 | }, |
| 286 | else => return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: constant generation of type {s}\n", .{ ty.zigTypeTag() }), |
| 287 | } |
| 288 | |
| 289 | return result_id; |
| 290 | } |
| 291 | |
| 292 | fn getOrGenType(self: *DeclGen, ty: Type) Error!u32 { |
| 54 | 293 | // We can't use getOrPut here so we can recursively generate types. |
| 55 | | if (self.types.get(t)) |already_generated| { |
| 294 | if (self.types.get(ty)) |already_generated| { |
| 56 | 295 | return already_generated; |
| 57 | 296 | } |
| 58 | 297 | |
| 59 | | const result = self.allocResultId(); |
| 298 | const target = self.module.getTarget(); |
| 299 | const code = &self.spv.types_globals_constants; |
| 300 | const result_id = self.spv.allocResultId(); |
| 60 | 301 | |
| 61 | | switch (t.zigTypeTag()) { |
| 62 | | .Void => try writeInstruction(&self.types_and_globals, .OpTypeVoid, &[_]u32{ result }), |
| 63 | | .Bool => try writeInstruction(&self.types_and_globals, .OpTypeBool, &[_]u32{ result }), |
| 302 | switch (ty.zigTypeTag()) { |
| 303 | .Void => try writeInstruction(code, .OpTypeVoid, &[_]u32{ result_id }), |
| 304 | .Bool => try writeInstruction(code, .OpTypeBool, &[_]u32{ result_id }), |
| 64 | 305 | .Int => { |
| 65 | | const int_info = t.intInfo(self.target); |
| 66 | | try writeInstruction(&self.types_and_globals, .OpTypeInt, &[_]u32{ |
| 67 | | result, |
| 68 | | int_info.bits, |
| 306 | const int_info = ty.intInfo(target); |
| 307 | const backing_bits = self.backingIntBits(int_info.bits) orelse { |
| 308 | // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits. |
| 309 | return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement composite ints {}", .{ ty }); |
| 310 | }; |
| 311 | |
| 312 | // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here. |
| 313 | try writeInstruction(code, .OpTypeInt, &[_]u32{ |
| 314 | result_id, |
| 315 | backing_bits, |
| 69 | 316 | switch (int_info.signedness) { |
| 70 | 317 | .unsigned => 0, |
| 71 | 318 | .signed => 1, |
| 72 | 319 | }, |
| 73 | 320 | }); |
| 74 | 321 | }, |
| 75 | | // TODO: Verify that floatBits() will be correct. |
| 76 | | .Float => try writeInstruction(&self.types_and_globals, .OpTypeFloat, &[_]u32{ result, t.floatBits(self.target) }), |
| 322 | .Float => { |
| 323 | // We can (and want) not really emulate floating points with other floating point types like with the integer types, |
| 324 | // so if the float is not supported, just return an error. |
| 325 | const bits = ty.floatBits(target); |
| 326 | const supported = switch (bits) { |
| 327 | 16 => Target.spirv.featureSetHas(target.cpu.features, .Float16), |
| 328 | // 32-bit floats are always supported (see spec, 2.16.1, Data rules). |
| 329 | 32 => true, |
| 330 | 64 => Target.spirv.featureSetHas(target.cpu.features, .Float64), |
| 331 | else => false, |
| 332 | }; |
| 333 | |
| 334 | if (!supported) { |
| 335 | return self.fail(.{.node_offset = 0}, "Floating point width of {} bits is not supported for the current SPIR-V feature set", .{ bits }); |
| 336 | } |
| 337 | |
| 338 | try writeInstruction(code, .OpTypeFloat, &[_]u32{ result_id, bits }); |
| 339 | }, |
| 340 | .Fn => { |
| 341 | // We only support zig-calling-convention functions, no varargs. |
| 342 | if (ty.fnCallingConvention() != .Unspecified) |
| 343 | return self.fail(.{.node_offset = 0}, "Unsupported calling convention for SPIR-V", .{}); |
| 344 | if (ty.fnIsVarArgs()) |
| 345 | return self.fail(.{.node_offset = 0}, "VarArgs unsupported for SPIR-V", .{}); |
| 346 | |
| 347 | // In order to avoid a temporary here, first generate all the required types and then simply look them up |
| 348 | // when generating the function type. |
| 349 | const params = ty.fnParamLen(); |
| 350 | var i: usize = 0; |
| 351 | while (i < params) : (i += 1) { |
| 352 | _ = try self.getOrGenType(ty.fnParamType(i)); |
| 353 | } |
| 354 | |
| 355 | const return_type_id = try self.getOrGenType(ty.fnReturnType()); |
| 356 | |
| 357 | // result id + result type id + parameter type ids. |
| 358 | try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u32, ty.fnParamLen()) ); |
| 359 | try code.appendSlice(&.{ result_id, return_type_id }); |
| 360 | |
| 361 | i = 0; |
| 362 | while (i < params) : (i += 1) { |
| 363 | const param_type_id = self.types.get(ty.fnParamType(i)).?; |
| 364 | try code.append(param_type_id); |
| 365 | } |
| 366 | }, |
| 367 | .Vector => { |
| 368 | // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations |
| 369 | // which work on them), so simply use those. |
| 370 | // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way. |
| 371 | // "composite integers" (larger than the largest supported native type) can probably be represented by an array of vectors. |
| 372 | // TODO: The SPIR-V spec mentions that vector sizes may be quite restricted! look into which we can use, and whether OpTypeVector |
| 373 | // is adequate at all for this. |
| 374 | |
| 375 | // TODO: Vectors are not yet supported by the self-hosted compiler itself it seems. |
| 376 | return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement type Vector", .{}); |
| 377 | }, |
| 77 | 378 | .Null, |
| 78 | 379 | .Undefined, |
| 79 | 380 | .EnumLiteral, |
| ... | ... | @@ -84,23 +385,197 @@ pub const SPIRVModule = struct { |
| 84 | 385 | |
| 85 | 386 | .BoundFn => unreachable, // this type will be deleted from the language. |
| 86 | 387 | |
| 87 | | else => return error.TODO, |
| 388 | else => |tag| return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement type {}s", .{ tag }), |
| 88 | 389 | } |
| 89 | 390 | |
| 90 | | try self.types.put(t, result); |
| 91 | | return result; |
| 391 | try self.types.putNoClobber(ty, result_id); |
| 392 | return result_id; |
| 92 | 393 | } |
| 93 | 394 | |
| 94 | | pub fn gen(self: *SPIRVModule, decl: *Decl) !void { |
| 95 | | const typed_value = decl.typed_value.most_recent.typed_value; |
| 395 | pub fn gen(self: *DeclGen) !void { |
| 396 | const result_id = self.decl.fn_link.spirv.id; |
| 397 | const tv = self.decl.typed_value.most_recent.typed_value; |
| 96 | 398 | |
| 97 | | switch (typed_value.ty.zigTypeTag()) { |
| 98 | | .Fn => { |
| 99 | | log.debug("Generating code for function '{s}'", .{ std.mem.spanZ(decl.name) }); |
| 399 | if (tv.val.castTag(.function)) |func_payload| { |
| 400 | std.debug.assert(tv.ty.zigTypeTag() == .Fn); |
| 401 | const prototype_id = try self.getOrGenType(tv.ty); |
| 402 | try writeInstruction(&self.spv.fn_decls, .OpFunction, &[_]u32{ |
| 403 | self.types.get(tv.ty.fnReturnType()).?, // This type should be generated along with the prototype. |
| 404 | result_id, |
| 405 | @bitCast(u32, spec.FunctionControl{}), // TODO: We can set inline here if the type requires it. |
| 406 | prototype_id, |
| 407 | }); |
| 100 | 408 | |
| 101 | | _ = try self.getOrGenType(typed_value.ty.fnReturnType()); |
| 102 | | }, |
| 103 | | else => return error.TODO, |
| 409 | const params = tv.ty.fnParamLen(); |
| 410 | var i: usize = 0; |
| 411 | |
| 412 | try self.args.ensureCapacity(params); |
| 413 | while (i < params) : (i += 1) { |
| 414 | const param_type_id = self.types.get(tv.ty.fnParamType(i)).?; |
| 415 | const arg_result_id = self.spv.allocResultId(); |
| 416 | try writeInstruction(&self.spv.fn_decls, .OpFunctionParameter, &[_]u32{ param_type_id, arg_result_id }); |
| 417 | self.args.appendAssumeCapacity(arg_result_id); |
| 418 | } |
| 419 | |
| 420 | // TODO: This could probably be done in a better way... |
| 421 | const root_block_id = self.spv.allocResultId(); |
| 422 | _ = try writeInstruction(&self.spv.fn_decls, .OpLabel, &[_]u32{root_block_id}); |
| 423 | try self.genBody(func_payload.data.body); |
| 424 | |
| 425 | try writeInstruction(&self.spv.fn_decls, .OpFunctionEnd, &[_]u32{}); |
| 426 | } else { |
| 427 | return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: generate decl type {}", .{ tv.ty.zigTypeTag() }); |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | fn genBody(self: *DeclGen, body: ir.Body) !void { |
| 432 | for (body.instructions) |inst| { |
| 433 | const maybe_result_id = try self.genInst(inst); |
| 434 | if (maybe_result_id) |result_id| |
| 435 | try self.values.putNoClobber(inst, result_id); |
| 104 | 436 | } |
| 105 | 437 | } |
| 438 | |
| 439 | fn genInst(self: *DeclGen, inst: *Inst) !?u32 { |
| 440 | return switch (inst.tag) { |
| 441 | .add, .addwrap => try self.genBinOp(inst.castTag(.add).?), |
| 442 | .sub, .subwrap => try self.genBinOp(inst.castTag(.sub).?), |
| 443 | .mul, .mulwrap => try self.genBinOp(inst.castTag(.mul).?), |
| 444 | .div => try self.genBinOp(inst.castTag(.div).?), |
| 445 | .bit_and => try self.genBinOp(inst.castTag(.bit_and).?), |
| 446 | .bit_or => try self.genBinOp(inst.castTag(.bit_or).?), |
| 447 | .xor => try self.genBinOp(inst.castTag(.xor).?), |
| 448 | .cmp_eq => try self.genBinOp(inst.castTag(.cmp_eq).?), |
| 449 | .cmp_neq => try self.genBinOp(inst.castTag(.cmp_neq).?), |
| 450 | .cmp_gt => try self.genBinOp(inst.castTag(.cmp_gt).?), |
| 451 | .cmp_gte => try self.genBinOp(inst.castTag(.cmp_gte).?), |
| 452 | .cmp_lt => try self.genBinOp(inst.castTag(.cmp_lt).?), |
| 453 | .cmp_lte => try self.genBinOp(inst.castTag(.cmp_lte).?), |
| 454 | .bool_and => try self.genBinOp(inst.castTag(.bool_and).?), |
| 455 | .bool_or => try self.genBinOp(inst.castTag(.bool_or).?), |
| 456 | .not => try self.genUnOp(inst.castTag(.not).?), |
| 457 | .arg => self.genArg(), |
| 458 | // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them |
| 459 | // throughout the IR. |
| 460 | .breakpoint => null, |
| 461 | .dbg_stmt => null, |
| 462 | .ret => self.genRet(inst.castTag(.ret).?), |
| 463 | .retvoid => self.genRetVoid(), |
| 464 | .unreach => self.genUnreach(), |
| 465 | else => self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: implement inst {}", .{inst.tag}), |
| 466 | }; |
| 467 | } |
| 468 | |
| 469 | fn genBinOp(self: *DeclGen, inst: *Inst.BinOp) !u32 { |
| 470 | // TODO: Will lhs and rhs have the same type? |
| 471 | const lhs_id = try self.resolve(inst.lhs); |
| 472 | const rhs_id = try self.resolve(inst.rhs); |
| 473 | |
| 474 | const result_id = self.spv.allocResultId(); |
| 475 | const result_type_id = try self.getOrGenType(inst.base.ty); |
| 476 | |
| 477 | // TODO: Is the result the same as the argument types? |
| 478 | // This is supposed to be the case for SPIR-V. |
| 479 | std.debug.assert(inst.rhs.ty.eql(inst.lhs.ty)); |
| 480 | std.debug.assert(inst.base.ty.tag() == .bool or inst.base.ty.eql(inst.lhs.ty)); |
| 481 | |
| 482 | // Binary operations are generally applicable to both scalar and vector operations in SPIR-V, but int and float |
| 483 | // versions of operations require different opcodes. |
| 484 | // For operations which produce bools, the information of inst.base.ty is not useful, so just pick either operand |
| 485 | // instead. |
| 486 | const info = try self.arithmeticTypeInfo(inst.lhs.ty); |
| 487 | |
| 488 | if (info.class == .composite_integer) |
| 489 | return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: binary operations for composite integers", .{}); |
| 490 | |
| 491 | const is_bool = info.class == .bool; |
| 492 | const is_float = info.class == .float; |
| 493 | const is_signed = info.signedness == .signed; |
| 494 | // **Note**: All these operations must be valid for vectors of floats, integers and bools as well! |
| 495 | // For floating points, we generally want ordered operations (which return false if either operand is nan). |
| 496 | const opcode = switch (inst.base.tag) { |
| 497 | // The regular integer operations are all defined for wrapping. Since theyre only relevant for integers, |
| 498 | // we can just switch on both cases here. |
| 499 | .add, .addwrap => if (is_float) Opcode.OpFAdd else Opcode.OpIAdd, |
| 500 | .sub, .subwrap => if (is_float) Opcode.OpFSub else Opcode.OpISub, |
| 501 | .mul, .mulwrap => if (is_float) Opcode.OpFMul else Opcode.OpIMul, |
| 502 | // TODO: Trap if divisor is 0? |
| 503 | // TODO: Figure out of OpSDiv for unsigned/OpUDiv for signed does anything useful. |
| 504 | // => Those are probably for divTrunc and divFloor, though the compiler does not yet generate those. |
| 505 | // => TODO: Figure out how those work on the SPIR-V side. |
| 506 | // => TODO: Test these. |
| 507 | .div => if (is_float) Opcode.OpFDiv else if (is_signed) Opcode.OpSDiv else Opcode.OpUDiv, |
| 508 | // Only integer versions for these. |
| 509 | .bit_and => Opcode.OpBitwiseAnd, |
| 510 | .bit_or => Opcode.OpBitwiseOr, |
| 511 | .xor => Opcode.OpBitwiseXor, |
| 512 | // Int/bool/float -> bool operations. |
| 513 | .cmp_eq => if (is_float) Opcode.OpFOrdEqual else if (is_bool) Opcode.OpLogicalEqual else Opcode.OpIEqual, |
| 514 | .cmp_neq => if (is_float) Opcode.OpFOrdNotEqual else if (is_bool) Opcode.OpLogicalNotEqual else Opcode.OpINotEqual, |
| 515 | // Int/float -> bool operations. |
| 516 | // TODO: Verify that these OpFOrd type operations produce the right value. |
| 517 | // TODO: Is there a more fundamental difference between OpU and OpS operations here than just the type? |
| 518 | .cmp_gt => if (is_float) Opcode.OpFOrdGreaterThan else if (is_signed) Opcode.OpSGreaterThan else Opcode.OpUGreaterThan, |
| 519 | .cmp_gte => if (is_float) Opcode.OpFOrdGreaterThanEqual else if (is_signed) Opcode.OpSGreaterThanEqual else Opcode.OpUGreaterThanEqual, |
| 520 | .cmp_lt => if (is_float) Opcode.OpFOrdLessThan else if (is_signed) Opcode.OpSLessThan else Opcode.OpULessThan, |
| 521 | .cmp_lte => if (is_float) Opcode.OpFOrdLessThanEqual else if (is_signed) Opcode.OpSLessThanEqual else Opcode.OpULessThanEqual, |
| 522 | // Bool -> bool operations. |
| 523 | .bool_and => Opcode.OpLogicalAnd, |
| 524 | .bool_or => Opcode.OpLogicalOr, |
| 525 | else => unreachable, |
| 526 | }; |
| 527 | |
| 528 | try writeInstruction(&self.spv.fn_decls, opcode, &[_]u32{ result_type_id, result_id, lhs_id, rhs_id }); |
| 529 | |
| 530 | // TODO: Trap on overflow? Probably going to be annoying. |
| 531 | // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap. |
| 532 | |
| 533 | if (info.class != .strange_integer) |
| 534 | return result_id; |
| 535 | |
| 536 | return self.fail(.{.node_offset = 0}, "TODO: SPIR-V backend: strange integer operation mask", .{}); |
| 537 | } |
| 538 | |
| 539 | fn genUnOp(self: *DeclGen, inst: *Inst.UnOp) !u32 { |
| 540 | const operand_id = try self.resolve(inst.operand); |
| 541 | |
| 542 | const result_id = self.spv.allocResultId(); |
| 543 | const result_type_id = try self.getOrGenType(inst.base.ty); |
| 544 | |
| 545 | const info = try self.arithmeticTypeInfo(inst.operand.ty); |
| 546 | |
| 547 | const opcode = switch (inst.base.tag) { |
| 548 | // Bool -> bool |
| 549 | .not => Opcode.OpLogicalNot, |
| 550 | else => unreachable, |
| 551 | }; |
| 552 | |
| 553 | try writeInstruction(&self.spv.fn_decls, opcode, &[_]u32{ result_type_id, result_id, operand_id }); |
| 554 | |
| 555 | return result_id; |
| 556 | } |
| 557 | |
| 558 | fn genArg(self: *DeclGen) u32 { |
| 559 | defer self.next_arg_index += 1; |
| 560 | return self.args.items[self.next_arg_index]; |
| 561 | } |
| 562 | |
| 563 | fn genRet(self: *DeclGen, inst: *Inst.UnOp) !?u32 { |
| 564 | const operand_id = try self.resolve(inst.operand); |
| 565 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |
| 566 | try writeInstruction(&self.spv.fn_decls, .OpReturnValue, &[_]u32{ operand_id }); |
| 567 | return null; |
| 568 | } |
| 569 | |
| 570 | fn genRetVoid(self: *DeclGen) !?u32 { |
| 571 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |
| 572 | try writeInstruction(&self.spv.fn_decls, .OpReturn, &[_]u32{}); |
| 573 | return null; |
| 574 | } |
| 575 | |
| 576 | fn genUnreach(self: *DeclGen) !?u32 { |
| 577 | // TODO: This instruction needs to be the last in a block. Is that guaranteed? |
| 578 | try writeInstruction(&self.spv.fn_decls, .OpUnreachable, &[_]u32{}); |
| 579 | return null; |
| 580 | } |
| 106 | 581 | }; |