| ... | @@ -1,44 +1,50 @@ | ... | @@ -1,44 +1,50 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const Allocator = std.mem.Allocator; | 2 | const Allocator = std.mem.Allocator; |
| | 3 | const Target = std.Target; |
| 3 | const log = std.log.scoped(.codegen); | 4 | const log = std.log.scoped(.codegen); |
| 4 | | 5 | |
| 5 | const spec = @import("spirv/spec.zig"); | 6 | const spec = @import("spirv/spec.zig"); |
| | 7 | const Opcode = spec.Opcode; |
| | 8 | |
| 6 | const Module = @import("../Module.zig"); | 9 | const Module = @import("../Module.zig"); |
| 7 | const Decl = Module.Decl; | 10 | const Decl = Module.Decl; |
| 8 | const Type = @import("../type.zig").Type; | 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 | 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); |
| | 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 { | 25 | pub fn writeInstruction(code: *std.ArrayList(u32), opcode: Opcode, args: []const u32) !void { |
| 13 | const word_count = @intCast(u32, args.len + 1); | 26 | try writeOpcode(code, opcode, @intCast(u32, args.len)); |
| 14 | try code.append((word_count << 16) | @enumToInt(instr)); | | |
| 15 | try code.appendSlice(args); | 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 | pub const SPIRVModule = struct { | 32 | pub const SPIRVModule = struct { |
| 19 | next_result_id: u32 = 0, | 33 | next_result_id: u32, |
| 20 | | 34 | types_globals_constants: std.ArrayList(u32), |
| 21 | target: std.Target, | | |
| 22 | | | |
| 23 | types: TypeMap, | | |
| 24 | | | |
| 25 | types_and_globals: std.ArrayList(u32), | | |
| 26 | fn_decls: std.ArrayList(u32), | 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 | return .{ | 38 | return .{ |
| 30 | .target = target, | 39 | .next_result_id = 1, // 0 is an invalid SPIR-V result ID. |
| 31 | .types = TypeMap.init(allocator), | 40 | .types_globals_constants = std.ArrayList(u32).init(allocator), |
| 32 | .types_and_globals = std.ArrayList(u32).init(allocator), | | |
| 33 | .fn_decls = std.ArrayList(u32).init(allocator), | 41 | .fn_decls = std.ArrayList(u32).init(allocator), |
| 34 | }; | 42 | }; |
| 35 | } | 43 | } |
| 36 | | 44 | |
| 37 | pub fn deinit(self: *SPIRVModule) void { | 45 | pub fn deinit(self: *SPIRVModule) void { |
| | 46 | self.types_globals_constants.deinit(); |
| 38 | self.fn_decls.deinit(); | 47 | self.fn_decls.deinit(); |
| 39 | self.types_and_globals.deinit(); | | |
| 40 | self.types.deinit(); | | |
| 41 | self.* = undefined; | | |
| 42 | } | 48 | } |
| 43 | | 49 | |
| 44 | pub fn allocResultId(self: *SPIRVModule) u32 { | 50 | pub fn allocResultId(self: *SPIRVModule) u32 { |
| ... | @@ -49,31 +55,326 @@ pub const SPIRVModule = struct { | ... | @@ -49,31 +55,326 @@ pub const SPIRVModule = struct { |
| 49 | pub fn resultIdBound(self: *SPIRVModule) u32 { | 55 | pub fn resultIdBound(self: *SPIRVModule) u32 { |
| 50 | return self.next_result_id; | 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 | // We can't use getOrPut here so we can recursively generate types. | 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 | return already_generated; | 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()) { | 302 | switch (ty.zigTypeTag()) { |
| 62 | .Void => try writeInstruction(&self.types_and_globals, .OpTypeVoid, &[_]u32{ result }), | 303 | .Void => try writeInstruction(code, .OpTypeVoid, &[_]u32{ result_id }), |
| 63 | .Bool => try writeInstruction(&self.types_and_globals, .OpTypeBool, &[_]u32{ result }), | 304 | .Bool => try writeInstruction(code, .OpTypeBool, &[_]u32{ result_id }), |
| 64 | .Int => { | 305 | .Int => { |
| 65 | const int_info = t.intInfo(self.target); | 306 | const int_info = ty.intInfo(target); |
| 66 | try writeInstruction(&self.types_and_globals, .OpTypeInt, &[_]u32{ | 307 | const backing_bits = self.backingIntBits(int_info.bits) orelse { |
| 67 | result, | 308 | // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits. |
| 68 | int_info.bits, | 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 | switch (int_info.signedness) { | 316 | switch (int_info.signedness) { |
| 70 | .unsigned => 0, | 317 | .unsigned => 0, |
| 71 | .signed => 1, | 318 | .signed => 1, |
| 72 | }, | 319 | }, |
| 73 | }); | 320 | }); |
| 74 | }, | 321 | }, |
| 75 | // TODO: Verify that floatBits() will be correct. | 322 | .Float => { |
| 76 | .Float => try writeInstruction(&self.types_and_globals, .OpTypeFloat, &[_]u32{ result, t.floatBits(self.target) }), | 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 | .Null, | 378 | .Null, |
| 78 | .Undefined, | 379 | .Undefined, |
| 79 | .EnumLiteral, | 380 | .EnumLiteral, |
| ... | @@ -84,23 +385,197 @@ pub const SPIRVModule = struct { | ... | @@ -84,23 +385,197 @@ pub const SPIRVModule = struct { |
| 84 | | 385 | |
| 85 | .BoundFn => unreachable, // this type will be deleted from the language. | 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); | 391 | try self.types.putNoClobber(ty, result_id); |
| 91 | return result; | 392 | return result_id; |
| 92 | } | 393 | } |
| 93 | | 394 | |
| 94 | pub fn gen(self: *SPIRVModule, decl: *Decl) !void { | 395 | pub fn gen(self: *DeclGen) !void { |
| 95 | const typed_value = decl.typed_value.most_recent.typed_value; | 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()) { | 399 | if (tv.val.castTag(.function)) |func_payload| { |
| 98 | .Fn => { | 400 | std.debug.assert(tv.ty.zigTypeTag() == .Fn); |
| 99 | log.debug("Generating code for function '{s}'", .{ std.mem.spanZ(decl.name) }); | 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()); | 409 | const params = tv.ty.fnParamLen(); |
| 102 | }, | 410 | var i: usize = 0; |
| 103 | else => return error.TODO, | 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 | }; |