zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 4ce5de75 (46818B) - Raw


      1 const std = @import("std");
      2 const Allocator = std.mem.Allocator;
      3 const Target = std.Target;
      4 const log = std.log.scoped(.codegen);
      5 const assert = std.debug.assert;
      6 
      7 const spec = @import("spirv/spec.zig");
      8 const Opcode = spec.Opcode;
      9 
     10 const Module = @import("../Module.zig");
     11 const Decl = Module.Decl;
     12 const Type = @import("../type.zig").Type;
     13 const Value = @import("../value.zig").Value;
     14 const LazySrcLoc = Module.LazySrcLoc;
     15 const ir = @import("../air.zig");
     16 const Inst = ir.Inst;
     17 
     18 pub const Word = u32;
     19 pub const ResultId = u32;
     20 
     21 pub const TypeMap = std.HashMap(Type, u32, Type.HashContext, std.hash_map.default_max_load_percentage);
     22 pub const InstMap = std.AutoHashMap(*Inst, ResultId);
     23 
     24 const IncomingBlock = struct {
     25     src_label_id: ResultId,
     26     break_value_id: ResultId,
     27 };
     28 
     29 pub const BlockMap = std.AutoHashMap(*Inst.Block, struct {
     30     label_id: ResultId,
     31     incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
     32 });
     33 
     34 pub fn writeOpcode(code: *std.ArrayList(Word), opcode: Opcode, arg_count: u16) !void {
     35     const word_count: Word = arg_count + 1;
     36     try code.append((word_count << 16) | @enumToInt(opcode));
     37 }
     38 
     39 pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word) !void {
     40     try writeOpcode(code, opcode, @intCast(u16, args.len));
     41     try code.appendSlice(args);
     42 }
     43 
     44 pub fn writeInstructionWithString(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word, str: []const u8) !void {
     45     // Str needs to be written zero-terminated, so we need to add one to the length.
     46     const zero_terminated_len = str.len + 1;
     47     const str_words = (zero_terminated_len + @sizeOf(Word) - 1) / @sizeOf(Word);
     48 
     49     try writeOpcode(code, opcode, @intCast(u16, args.len + str_words));
     50     try code.ensureUnusedCapacity(args.len + str_words);
     51     code.appendSliceAssumeCapacity(args);
     52 
     53     // TODO: Not actually sure whether this is correct for big-endian.
     54     // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal
     55     var i: usize = 0;
     56     while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
     57         var word: Word = 0;
     58 
     59         var j: usize = 0;
     60         while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
     61             word |= @as(Word, str[i + j]) << @intCast(std.math.Log2Int(Word), j * std.meta.bitCount(u8));
     62         }
     63 
     64         code.appendAssumeCapacity(word);
     65     }
     66 }
     67 
     68 /// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information.
     69 /// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
     70 /// of data which needs to be persistent over different calls to Decl code generation.
     71 pub const SPIRVModule = struct {
     72     /// A general-purpose allocator which may be used to allocate temporary resources required for compilation.
     73     gpa: *Allocator,
     74 
     75     /// The parent module.
     76     module: *Module,
     77 
     78     /// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these.
     79     next_result_id: ResultId,
     80 
     81     /// Code of the actual SPIR-V binary, divided into the relevant logical sections.
     82     /// Note: To save some bytes, these could also be unmanaged, but since there is only one instance of SPIRVModule
     83     /// and this removes some clutter in the rest of the backend, it's fine like this.
     84     binary: struct {
     85         /// OpCapability and OpExtension instructions (in that order).
     86         capabilities_and_extensions: std.ArrayList(Word),
     87 
     88         /// OpString, OpSourceExtension, OpSource, OpSourceContinued.
     89         debug_strings: std.ArrayList(Word),
     90 
     91         /// Type declaration instructions, constant instructions, global variable declarations, OpUndef instructions.
     92         types_globals_constants: std.ArrayList(Word),
     93 
     94         /// Regular functions.
     95         fn_decls: std.ArrayList(Word),
     96     },
     97 
     98     /// Global type cache to reduce the amount of generated types.
     99     types: TypeMap,
    100 
    101     /// Cache for results of OpString instructions for module file names fed to OpSource.
    102     /// Since OpString is pretty much only used for those, we don't need to keep track of all strings,
    103     /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
    104     file_names: std.StringHashMap(ResultId),
    105 
    106     pub fn init(gpa: *Allocator, module: *Module) SPIRVModule {
    107         return .{
    108             .gpa = gpa,
    109             .module = module,
    110             .next_result_id = 1, // 0 is an invalid SPIR-V result ID.
    111             .binary = .{
    112                 .capabilities_and_extensions = std.ArrayList(Word).init(gpa),
    113                 .debug_strings = std.ArrayList(Word).init(gpa),
    114                 .types_globals_constants = std.ArrayList(Word).init(gpa),
    115                 .fn_decls = std.ArrayList(Word).init(gpa),
    116             },
    117             .types = TypeMap.init(gpa),
    118             .file_names = std.StringHashMap(ResultId).init(gpa),
    119         };
    120     }
    121 
    122     pub fn deinit(self: *SPIRVModule) void {
    123         self.file_names.deinit();
    124         self.types.deinit();
    125 
    126         self.binary.fn_decls.deinit();
    127         self.binary.types_globals_constants.deinit();
    128         self.binary.debug_strings.deinit();
    129         self.binary.capabilities_and_extensions.deinit();
    130     }
    131 
    132     pub fn allocResultId(self: *SPIRVModule) Word {
    133         defer self.next_result_id += 1;
    134         return self.next_result_id;
    135     }
    136 
    137     pub fn resultIdBound(self: *SPIRVModule) Word {
    138         return self.next_result_id;
    139     }
    140 
    141     fn resolveSourceFileName(self: *SPIRVModule, decl: *Decl) !ResultId {
    142         const path = decl.namespace.file_scope.sub_file_path;
    143         const result = try self.file_names.getOrPut(path);
    144         if (!result.found_existing) {
    145             result.value_ptr.* = self.allocResultId();
    146             try writeInstructionWithString(&self.binary.debug_strings, .OpString, &[_]Word{result.value_ptr.*}, path);
    147             try writeInstruction(&self.binary.debug_strings, .OpSource, &[_]Word{
    148                 @enumToInt(spec.SourceLanguage.Unknown), // TODO: Register Zig source language.
    149                 0, // TODO: Zig version as u32?
    150                 result.value_ptr.*,
    151             });
    152         }
    153 
    154         return result.value_ptr.*;
    155     }
    156 };
    157 
    158 /// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
    159 pub const DeclGen = struct {
    160     /// The SPIR-V module  code should be put in.
    161     spv: *SPIRVModule,
    162 
    163     /// An array of function argument result-ids. Each index corresponds with the function argument of the same index.
    164     args: std.ArrayList(ResultId),
    165 
    166     /// A counter to keep track of how many `arg` instructions we've seen yet.
    167     next_arg_index: u32,
    168 
    169     /// A map keeping track of which instruction generated which result-id.
    170     inst_results: InstMap,
    171 
    172     /// We need to keep track of result ids for block labels, as well as the 'incoming' blocks for a block.
    173     blocks: BlockMap,
    174 
    175     /// The label of the SPIR-V block we are currently generating.
    176     current_block_label_id: ResultId,
    177 
    178     /// The actual instructions for this function. We need to declare all locals in the first block, and because we don't
    179     /// know which locals there are going to be, we're just going to generate everything after the locals-section in this array.
    180     /// Note: It will not contain OpFunction, OpFunctionParameter, OpVariable and the initial OpLabel. These will be generated
    181     /// into spv.binary.fn_decls directly.
    182     code: std.ArrayList(Word),
    183 
    184     /// The decl we are currently generating code for.
    185     decl: *Decl,
    186 
    187     /// If `gen` returned `Error.AnalysisFail`, this contains an explanatory message. Memory is owned by
    188     /// `module.gpa`.
    189     error_msg: ?*Module.ErrorMsg,
    190 
    191     /// Possible errors the `gen` function may return.
    192     const Error = error{ AnalysisFail, OutOfMemory };
    193 
    194     /// This structure is used to return information about a type typically used for arithmetic operations.
    195     /// These types may either be integers, floats, or a vector of these. Most scalar operations also work on vectors,
    196     /// so we can easily represent those as arithmetic types.
    197     /// If the type is a scalar, 'inner type' refers to the scalar type. Otherwise, if its a vector, it refers
    198     /// to the vector's element type.
    199     const ArithmeticTypeInfo = struct {
    200         /// A classification of the inner type.
    201         const Class = enum {
    202             /// A boolean.
    203             bool,
    204 
    205             /// A regular, **native**, integer.
    206             /// This is only returned when the backend supports this int as a native type (when
    207             /// the relevant capability is enabled).
    208             integer,
    209 
    210             /// A regular float. These are all required to be natively supported. Floating points for
    211             /// which the relevant capability is not enabled are not emulated.
    212             float,
    213 
    214             /// An integer of a 'strange' size (which' bit size is not the same as its backing type. **Note**: this
    215             /// may **also** include power-of-2 integers for which the relevant capability is not enabled), but still
    216             /// within the limits of the largest natively supported integer type.
    217             strange_integer,
    218 
    219             /// An integer with more bits than the largest natively supported integer type.
    220             composite_integer,
    221         };
    222 
    223         /// The number of bits in the inner type.
    224         /// Note: this is the actual number of bits of the type, not the size of the backing integer.
    225         bits: u16,
    226 
    227         /// Whether the type is a vector.
    228         is_vector: bool,
    229 
    230         /// Whether the inner type is signed. Only relevant for integers.
    231         signedness: std.builtin.Signedness,
    232 
    233         /// A classification of the inner type. These scenarios
    234         /// will all have to be handled slightly different.
    235         class: Class,
    236     };
    237 
    238     /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, only set when `gen` is called.
    239     pub fn init(spv: *SPIRVModule) DeclGen {
    240         return .{
    241             .spv = spv,
    242             .args = std.ArrayList(ResultId).init(spv.gpa),
    243             .next_arg_index = undefined,
    244             .inst_results = InstMap.init(spv.gpa),
    245             .blocks = BlockMap.init(spv.gpa),
    246             .current_block_label_id = undefined,
    247             .code = std.ArrayList(Word).init(spv.gpa),
    248             .decl = undefined,
    249             .error_msg = undefined,
    250         };
    251     }
    252 
    253     /// Generate the code for `decl`. If a reportable error occured during code generation,
    254     /// a message is returned by this function. Callee owns the memory. If this function returns such
    255     /// a reportable error, it is valid to be called again for a different decl.
    256     pub fn gen(self: *DeclGen, decl: *Decl) !?*Module.ErrorMsg {
    257         // Reset internal resources, we don't want to re-allocate these.
    258         self.args.items.len = 0;
    259         self.next_arg_index = 0;
    260         self.inst_results.clearRetainingCapacity();
    261         self.blocks.clearRetainingCapacity();
    262         self.current_block_label_id = undefined;
    263         self.code.items.len = 0;
    264         self.decl = decl;
    265         self.error_msg = null;
    266 
    267         try self.genDecl();
    268         return self.error_msg;
    269     }
    270 
    271     /// Free resources owned by the DeclGen.
    272     pub fn deinit(self: *DeclGen) void {
    273         self.args.deinit();
    274         self.inst_results.deinit();
    275         self.blocks.deinit();
    276         self.code.deinit();
    277     }
    278 
    279     fn getTarget(self: *DeclGen) std.Target {
    280         return self.spv.module.getTarget();
    281     }
    282 
    283     fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {
    284         @setCold(true);
    285         const src_loc = src.toSrcLocWithDecl(self.decl);
    286         self.error_msg = try Module.ErrorMsg.create(self.spv.module.gpa, src_loc, format, args);
    287         return error.AnalysisFail;
    288     }
    289 
    290     fn resolve(self: *DeclGen, inst: *Inst) !ResultId {
    291         if (inst.value()) |val| {
    292             return self.genConstant(inst.src, inst.ty, val);
    293         }
    294 
    295         return self.inst_results.get(inst).?; // Instruction does not dominate all uses!
    296     }
    297 
    298     fn beginSPIRVBlock(self: *DeclGen, label_id: ResultId) !void {
    299         try writeInstruction(&self.code, .OpLabel, &[_]Word{label_id});
    300         self.current_block_label_id = label_id;
    301     }
    302 
    303     /// SPIR-V requires enabling specific integer sizes through capabilities, and so if they are not enabled, we need
    304     /// to emulate them in other instructions/types. This function returns, given an integer bit width (signed or unsigned, sign
    305     /// included), the width of the underlying type which represents it, given the enabled features for the current target.
    306     /// If the result is `null`, the largest type the target platform supports natively is not able to perform computations using
    307     /// that size. In this case, multiple elements of the largest type should be used.
    308     /// The backing type will be chosen as the smallest supported integer larger or equal to it in number of bits.
    309     /// The result is valid to be used with OpTypeInt.
    310     /// TODO: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
    311     /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
    312     /// TODO: Should the result of this function be cached?
    313     fn backingIntBits(self: *DeclGen, bits: u16) ?u16 {
    314         const target = self.getTarget();
    315 
    316         // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.
    317         std.debug.assert(bits != 0);
    318 
    319         // 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.
    320         // 32-bit integers are always supported (see spec, 2.16.1, Data rules).
    321         const ints = [_]struct { bits: u16, feature: ?Target.spirv.Feature }{
    322             .{ .bits = 8, .feature = .Int8 },
    323             .{ .bits = 16, .feature = .Int16 },
    324             .{ .bits = 32, .feature = null },
    325             .{ .bits = 64, .feature = .Int64 },
    326         };
    327 
    328         for (ints) |int| {
    329             const has_feature = if (int.feature) |feature|
    330                 Target.spirv.featureSetHas(target.cpu.features, feature)
    331             else
    332                 true;
    333 
    334             if (bits <= int.bits and has_feature) {
    335                 return int.bits;
    336             }
    337         }
    338 
    339         return null;
    340     }
    341 
    342     /// Return the amount of bits in the largest supported integer type. This is either 32 (always supported), or 64 (if
    343     /// the Int64 capability is enabled).
    344     /// Note: The extension SPV_INTEL_arbitrary_precision_integers allows any integer size (at least up to 32 bits).
    345     /// In theory that could also be used, but since the spec says that it only guarantees support up to 32-bit ints there
    346     /// is no way of knowing whether those are actually supported.
    347     /// TODO: Maybe this should be cached?
    348     fn largestSupportedIntBits(self: *DeclGen) u16 {
    349         const target = self.getTarget();
    350         return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
    351             64
    352         else
    353             32;
    354     }
    355 
    356     /// Checks whether the type is "composite int", an integer consisting of multiple native integers. These are represented by
    357     /// arrays of largestSupportedIntBits().
    358     /// Asserts `ty` is an integer.
    359     fn isCompositeInt(self: *DeclGen, ty: Type) bool {
    360         return self.backingIntBits(ty) == null;
    361     }
    362 
    363     fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo {
    364         const target = self.getTarget();
    365         return switch (ty.zigTypeTag()) {
    366             .Bool => ArithmeticTypeInfo{
    367                 .bits = 1, // Doesn't matter for this class.
    368                 .is_vector = false,
    369                 .signedness = .unsigned, // Technically, but doesn't matter for this class.
    370                 .class = .bool,
    371             },
    372             .Float => ArithmeticTypeInfo{
    373                 .bits = ty.floatBits(target),
    374                 .is_vector = false,
    375                 .signedness = .signed, // Technically, but doesn't matter for this class.
    376                 .class = .float,
    377             },
    378             .Int => blk: {
    379                 const int_info = ty.intInfo(target);
    380                 // TODO: Maybe it's useful to also return this value.
    381                 const maybe_backing_bits = self.backingIntBits(int_info.bits);
    382                 break :blk ArithmeticTypeInfo{ .bits = int_info.bits, .is_vector = false, .signedness = int_info.signedness, .class = if (maybe_backing_bits) |backing_bits|
    383                     if (backing_bits == int_info.bits)
    384                         ArithmeticTypeInfo.Class.integer
    385                     else
    386                         ArithmeticTypeInfo.Class.strange_integer
    387                 else
    388                     .composite_integer };
    389             },
    390             // As of yet, there is no vector support in the self-hosted compiler.
    391             .Vector => self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
    392             // TODO: For which types is this the case?
    393             else => self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}),
    394         };
    395     }
    396 
    397     /// Generate a constant representing `val`.
    398     /// TODO: Deduplication?
    399     fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId {
    400         const target = self.getTarget();
    401         const code = &self.spv.binary.types_globals_constants;
    402         const result_id = self.spv.allocResultId();
    403         const result_type_id = try self.genType(src, ty);
    404 
    405         if (val.isUndef()) {
    406             try writeInstruction(code, .OpUndef, &[_]Word{ result_type_id, result_id });
    407             return result_id;
    408         }
    409 
    410         switch (ty.zigTypeTag()) {
    411             .Int => {
    412                 const int_info = ty.intInfo(target);
    413                 const backing_bits = self.backingIntBits(int_info.bits) orelse {
    414                     // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
    415                     return self.fail(src, "TODO: SPIR-V backend: implement composite int constants for {}", .{ty});
    416                 };
    417 
    418                 // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any
    419                 // SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this
    420                 // might need to be updated.
    421                 std.debug.assert(self.largestSupportedIntBits() <= std.meta.bitCount(u64));
    422                 var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();
    423 
    424                 // Mask the low bits which make up the actual integer. This is to make sure that negative values
    425                 // only use the actual bits of the type.
    426                 // TODO: Should this be the backing type bits or the actual type bits?
    427                 int_bits &= (@as(u64, 1) << @intCast(u6, backing_bits)) - 1;
    428 
    429                 switch (backing_bits) {
    430                     0 => unreachable,
    431                     1...32 => try writeInstruction(code, .OpConstant, &[_]Word{
    432                         result_type_id,
    433                         result_id,
    434                         @truncate(u32, int_bits),
    435                     }),
    436                     33...64 => try writeInstruction(code, .OpConstant, &[_]Word{
    437                         result_type_id,
    438                         result_id,
    439                         @truncate(u32, int_bits),
    440                         @truncate(u32, int_bits >> @bitSizeOf(u32)),
    441                     }),
    442                     else => unreachable, // backing_bits is bounded by largestSupportedIntBits.
    443                 }
    444             },
    445             .Bool => {
    446                 const opcode: Opcode = if (val.toBool()) .OpConstantTrue else .OpConstantFalse;
    447                 try writeInstruction(code, opcode, &[_]Word{ result_type_id, result_id });
    448             },
    449             .Float => {
    450                 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
    451                 // would have exited at genType(ty).
    452 
    453                 // f16 and f32 require one word of storage. f64 requires 2, low-order first.
    454 
    455                 switch (ty.floatBits(target)) {
    456                     16 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u16, val.toFloat(f16)) }),
    457                     32 => try writeInstruction(code, .OpConstant, &[_]Word{ result_type_id, result_id, @bitCast(u32, val.toFloat(f32)) }),
    458                     64 => {
    459                         const float_bits = @bitCast(u64, val.toFloat(f64));
    460                         try writeInstruction(code, .OpConstant, &[_]Word{
    461                             result_type_id,
    462                             result_id,
    463                             @truncate(u32, float_bits),
    464                             @truncate(u32, float_bits >> @bitSizeOf(u32)),
    465                         });
    466                     },
    467                     128 => unreachable, // Filtered out in the call to genType.
    468                     // TODO: Insert case for long double when the layout for that is determined.
    469                     else => unreachable,
    470                 }
    471             },
    472             .Void => unreachable,
    473             else => return self.fail(src, "TODO: SPIR-V backend: constant generation of type {}", .{ty}),
    474         }
    475 
    476         return result_id;
    477     }
    478 
    479     fn genType(self: *DeclGen, src: LazySrcLoc, ty: Type) Error!ResultId {
    480         // We can't use getOrPut here so we can recursively generate types.
    481         if (self.spv.types.get(ty)) |already_generated| {
    482             return already_generated;
    483         }
    484 
    485         const target = self.getTarget();
    486         const code = &self.spv.binary.types_globals_constants;
    487         const result_id = self.spv.allocResultId();
    488 
    489         switch (ty.zigTypeTag()) {
    490             .Void => try writeInstruction(code, .OpTypeVoid, &[_]Word{result_id}),
    491             .Bool => try writeInstruction(code, .OpTypeBool, &[_]Word{result_id}),
    492             .Int => {
    493                 const int_info = ty.intInfo(target);
    494                 const backing_bits = self.backingIntBits(int_info.bits) orelse {
    495                     // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
    496                     return self.fail(src, "TODO: SPIR-V backend: implement composite int {}", .{ty});
    497                 };
    498 
    499                 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
    500                 try writeInstruction(code, .OpTypeInt, &[_]Word{
    501                     result_id,
    502                     backing_bits,
    503                     switch (int_info.signedness) {
    504                         .unsigned => 0,
    505                         .signed => 1,
    506                     },
    507                 });
    508             },
    509             .Float => {
    510                 // We can (and want) not really emulate floating points with other floating point types like with the integer types,
    511                 // so if the float is not supported, just return an error.
    512                 const bits = ty.floatBits(target);
    513                 const supported = switch (bits) {
    514                     16 => Target.spirv.featureSetHas(target.cpu.features, .Float16),
    515                     // 32-bit floats are always supported (see spec, 2.16.1, Data rules).
    516                     32 => true,
    517                     64 => Target.spirv.featureSetHas(target.cpu.features, .Float64),
    518                     else => false,
    519                 };
    520 
    521                 if (!supported) {
    522                     return self.fail(src, "Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
    523                 }
    524 
    525                 try writeInstruction(code, .OpTypeFloat, &[_]Word{ result_id, bits });
    526             },
    527             .Fn => {
    528                 // We only support zig-calling-convention functions, no varargs.
    529                 if (ty.fnCallingConvention() != .Unspecified)
    530                     return self.fail(src, "Unsupported calling convention for SPIR-V", .{});
    531                 if (ty.fnIsVarArgs())
    532                     return self.fail(src, "VarArgs unsupported for SPIR-V", .{});
    533 
    534                 // In order to avoid a temporary here, first generate all the required types and then simply look them up
    535                 // when generating the function type.
    536                 const params = ty.fnParamLen();
    537                 var i: usize = 0;
    538                 while (i < params) : (i += 1) {
    539                     _ = try self.genType(src, ty.fnParamType(i));
    540                 }
    541 
    542                 const return_type_id = try self.genType(src, ty.fnReturnType());
    543 
    544                 // result id + result type id + parameter type ids.
    545                 try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));
    546                 try code.appendSlice(&.{ result_id, return_type_id });
    547 
    548                 i = 0;
    549                 while (i < params) : (i += 1) {
    550                     const param_type_id = self.spv.types.get(ty.fnParamType(i)).?;
    551                     try code.append(param_type_id);
    552                 }
    553             },
    554             // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.
    555             .Pointer => return self.fail(src, "Cannot create pointer with unkown storage class", .{}),
    556             .Vector => {
    557                 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
    558                 // which work on them), so simply use those.
    559                 // Note: SPIR-V vectors only support bools, ints and floats, so pointer vectors need to be supported another way.
    560                 // "composite integers" (larger than the largest supported native type) can probably be represented by an array of vectors.
    561                 // TODO: The SPIR-V spec mentions that vector sizes may be quite restricted! look into which we can use, and whether OpTypeVector
    562                 // is adequate at all for this.
    563 
    564                 // TODO: Vectors are not yet supported by the self-hosted compiler itself it seems.
    565                 return self.fail(src, "TODO: SPIR-V backend: implement type Vector", .{});
    566             },
    567             .Null,
    568             .Undefined,
    569             .EnumLiteral,
    570             .ComptimeFloat,
    571             .ComptimeInt,
    572             .Type,
    573             => unreachable, // Must be const or comptime.
    574 
    575             .BoundFn => unreachable, // this type will be deleted from the language.
    576 
    577             else => |tag| return self.fail(src, "TODO: SPIR-V backend: implement type {}s", .{tag}),
    578         }
    579 
    580         try self.spv.types.putNoClobber(ty, result_id);
    581         return result_id;
    582     }
    583 
    584     /// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.
    585     /// TODO: The result of this needs to be cached.
    586     fn genPointerType(self: *DeclGen, src: LazySrcLoc, ty: Type, storage_class: spec.StorageClass) !ResultId {
    587         std.debug.assert(ty.zigTypeTag() == .Pointer);
    588 
    589         const code = &self.spv.binary.types_globals_constants;
    590         const result_id = self.spv.allocResultId();
    591 
    592         // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types
    593         // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.
    594         // These also relates to the pointer's address space.
    595         const child_id = try self.genType(src, ty.elemType());
    596 
    597         try writeInstruction(code, .OpTypePointer, &[_]Word{ result_id, @enumToInt(storage_class), child_id });
    598 
    599         return result_id;
    600     }
    601 
    602     fn genDecl(self: *DeclGen) !void {
    603         const decl = self.decl;
    604         const result_id = decl.fn_link.spirv.id;
    605 
    606         if (decl.val.castTag(.function)) |func_payload| {
    607             std.debug.assert(decl.ty.zigTypeTag() == .Fn);
    608             const prototype_id = try self.genType(.{ .node_offset = 0 }, decl.ty);
    609             try writeInstruction(&self.spv.binary.fn_decls, .OpFunction, &[_]Word{
    610                 self.spv.types.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
    611                 result_id,
    612                 @bitCast(Word, spec.FunctionControl{}), // TODO: We can set inline here if the type requires it.
    613                 prototype_id,
    614             });
    615 
    616             const params = decl.ty.fnParamLen();
    617             var i: usize = 0;
    618 
    619             try self.args.ensureCapacity(params);
    620             while (i < params) : (i += 1) {
    621                 const param_type_id = self.spv.types.get(decl.ty.fnParamType(i)).?;
    622                 const arg_result_id = self.spv.allocResultId();
    623                 try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionParameter, &[_]Word{ param_type_id, arg_result_id });
    624                 self.args.appendAssumeCapacity(arg_result_id);
    625             }
    626 
    627             // TODO: This could probably be done in a better way...
    628             const root_block_id = self.spv.allocResultId();
    629 
    630             // We need to generate the label directly in the fn_decls here because we're going to write the local variables after
    631             // here. Since we're not generating in self.code, we're just going to bypass self.beginSPIRVBlock here.
    632             try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]Word{root_block_id});
    633             self.current_block_label_id = root_block_id;
    634 
    635             try self.genBody(func_payload.data.body);
    636 
    637             // Append the actual code into the fn_decls section.
    638             try self.spv.binary.fn_decls.appendSlice(self.code.items);
    639             try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{});
    640         } else {
    641             return self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});
    642         }
    643     }
    644 
    645     fn genBody(self: *DeclGen, body: ir.Body) Error!void {
    646         for (body.instructions) |inst| {
    647             try self.genInst(inst);
    648         }
    649     }
    650 
    651     fn genInst(self: *DeclGen, inst: *Inst) !void {
    652         const result_id = switch (inst.tag) {
    653             .add, .addwrap => try self.genBinOp(inst.castTag(.add).?),
    654             .sub, .subwrap => try self.genBinOp(inst.castTag(.sub).?),
    655             .mul, .mulwrap => try self.genBinOp(inst.castTag(.mul).?),
    656             .div => try self.genBinOp(inst.castTag(.div).?),
    657             .bit_and => try self.genBinOp(inst.castTag(.bit_and).?),
    658             .bit_or => try self.genBinOp(inst.castTag(.bit_or).?),
    659             .xor => try self.genBinOp(inst.castTag(.xor).?),
    660             .cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?),
    661             .cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?),
    662             .cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?),
    663             .cmp_gte => try self.genCmp(inst.castTag(.cmp_gte).?),
    664             .cmp_lt => try self.genCmp(inst.castTag(.cmp_lt).?),
    665             .cmp_lte => try self.genCmp(inst.castTag(.cmp_lte).?),
    666             .bool_and => try self.genBinOp(inst.castTag(.bool_and).?),
    667             .bool_or => try self.genBinOp(inst.castTag(.bool_or).?),
    668             .not => try self.genUnOp(inst.castTag(.not).?),
    669             .alloc => try self.genAlloc(inst.castTag(.alloc).?),
    670             .arg => self.genArg(),
    671             .block => (try self.genBlock(inst.castTag(.block).?)) orelse return,
    672             .br => return try self.genBr(inst.castTag(.br).?),
    673             .br_void => return try self.genBrVoid(inst.castTag(.br_void).?),
    674             // TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them
    675             // throughout the IR.
    676             .breakpoint => return,
    677             .condbr => return try self.genCondBr(inst.castTag(.condbr).?),
    678             .constant => unreachable,
    679             .dbg_stmt => return try self.genDbgStmt(inst.castTag(.dbg_stmt).?),
    680             .load => try self.genLoad(inst.castTag(.load).?),
    681             .loop => return try self.genLoop(inst.castTag(.loop).?),
    682             .ret => return try self.genRet(inst.castTag(.ret).?),
    683             .retvoid => return try self.genRetVoid(),
    684             .store => return try self.genStore(inst.castTag(.store).?),
    685             .unreach => return try self.genUnreach(),
    686             else => return self.fail(inst.src, "TODO: SPIR-V backend: implement inst {s}", .{@tagName(inst.tag)}),
    687         };
    688 
    689         try self.inst_results.putNoClobber(inst, result_id);
    690     }
    691 
    692     fn genBinOp(self: *DeclGen, inst: *Inst.BinOp) !ResultId {
    693         // TODO: Will lhs and rhs have the same type?
    694         const lhs_id = try self.resolve(inst.lhs);
    695         const rhs_id = try self.resolve(inst.rhs);
    696 
    697         const result_id = self.spv.allocResultId();
    698         const result_type_id = try self.genType(inst.base.src, inst.base.ty);
    699 
    700         // TODO: Is the result the same as the argument types?
    701         // This is supposed to be the case for SPIR-V.
    702         std.debug.assert(inst.rhs.ty.eql(inst.lhs.ty));
    703         std.debug.assert(inst.base.ty.tag() == .bool or inst.base.ty.eql(inst.lhs.ty));
    704 
    705         // Binary operations are generally applicable to both scalar and vector operations in SPIR-V, but int and float
    706         // versions of operations require different opcodes.
    707         // For operations which produce bools, the information of inst.base.ty is not useful, so just pick either operand
    708         // instead.
    709         const info = try self.arithmeticTypeInfo(inst.lhs.ty);
    710 
    711         if (info.class == .composite_integer) {
    712             return self.fail(inst.base.src, "TODO: SPIR-V backend: binary operations for composite integers", .{});
    713         } else if (info.class == .strange_integer) {
    714             return self.fail(inst.base.src, "TODO: SPIR-V backend: binary operations for strange integers", .{});
    715         }
    716 
    717         const is_bool = info.class == .bool;
    718         const is_float = info.class == .float;
    719         const is_signed = info.signedness == .signed;
    720         // **Note**: All these operations must be valid for vectors as well!
    721         const opcode = switch (inst.base.tag) {
    722             // The regular integer operations are all defined for wrapping. Since theyre only relevant for integers,
    723             // we can just switch on both cases here.
    724             .add, .addwrap => if (is_float) Opcode.OpFAdd else Opcode.OpIAdd,
    725             .sub, .subwrap => if (is_float) Opcode.OpFSub else Opcode.OpISub,
    726             .mul, .mulwrap => if (is_float) Opcode.OpFMul else Opcode.OpIMul,
    727             // TODO: Trap if divisor is 0?
    728             // TODO: Figure out of OpSDiv for unsigned/OpUDiv for signed does anything useful.
    729             //  => Those are probably for divTrunc and divFloor, though the compiler does not yet generate those.
    730             //  => TODO: Figure out how those work on the SPIR-V side.
    731             //  => TODO: Test these.
    732             .div => if (is_float) Opcode.OpFDiv else if (is_signed) Opcode.OpSDiv else Opcode.OpUDiv,
    733             // Only integer versions for these.
    734             .bit_and => Opcode.OpBitwiseAnd,
    735             .bit_or => Opcode.OpBitwiseOr,
    736             .xor => Opcode.OpBitwiseXor,
    737             // Bool -> bool operations.
    738             .bool_and => Opcode.OpLogicalAnd,
    739             .bool_or => Opcode.OpLogicalOr,
    740             else => unreachable,
    741         };
    742 
    743         try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });
    744 
    745         // TODO: Trap on overflow? Probably going to be annoying.
    746         // TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
    747 
    748         if (info.class != .strange_integer)
    749             return result_id;
    750 
    751         return self.fail(inst.base.src, "TODO: SPIR-V backend: strange integer operation mask", .{});
    752     }
    753 
    754     fn genCmp(self: *DeclGen, inst: *Inst.BinOp) !ResultId {
    755         const lhs_id = try self.resolve(inst.lhs);
    756         const rhs_id = try self.resolve(inst.rhs);
    757 
    758         const result_id = self.spv.allocResultId();
    759         const result_type_id = try self.genType(inst.base.src, inst.base.ty);
    760 
    761         // All of these operations should be 2 equal types -> bool
    762         std.debug.assert(inst.rhs.ty.eql(inst.lhs.ty));
    763         std.debug.assert(inst.base.ty.tag() == .bool);
    764 
    765         // Comparisons are generally applicable to both scalar and vector operations in SPIR-V, but int and float
    766         // versions of operations require different opcodes.
    767         // Since inst.base.ty is always bool and so not very useful, and because both arguments must be the same, just get the info
    768         // from either of the operands.
    769         const info = try self.arithmeticTypeInfo(inst.lhs.ty);
    770 
    771         if (info.class == .composite_integer) {
    772             return self.fail(inst.base.src, "TODO: SPIR-V backend: binary operations for composite integers", .{});
    773         } else if (info.class == .strange_integer) {
    774             return self.fail(inst.base.src, "TODO: SPIR-V backend: comparison for strange integers", .{});
    775         }
    776 
    777         const is_bool = info.class == .bool;
    778         const is_float = info.class == .float;
    779         const is_signed = info.signedness == .signed;
    780 
    781         // **Note**: All these operations must be valid for vectors as well!
    782         // For floating points, we generally want ordered operations (which return false if either operand is nan).
    783         const opcode = switch (inst.base.tag) {
    784             .cmp_eq => if (is_float) Opcode.OpFOrdEqual else if (is_bool) Opcode.OpLogicalEqual else Opcode.OpIEqual,
    785             .cmp_neq => if (is_float) Opcode.OpFOrdNotEqual else if (is_bool) Opcode.OpLogicalNotEqual else Opcode.OpINotEqual,
    786             // TODO: Verify that these OpFOrd type operations produce the right value.
    787             // TODO: Is there a more fundamental difference between OpU and OpS operations here than just the type?
    788             .cmp_gt => if (is_float) Opcode.OpFOrdGreaterThan else if (is_signed) Opcode.OpSGreaterThan else Opcode.OpUGreaterThan,
    789             .cmp_gte => if (is_float) Opcode.OpFOrdGreaterThanEqual else if (is_signed) Opcode.OpSGreaterThanEqual else Opcode.OpUGreaterThanEqual,
    790             .cmp_lt => if (is_float) Opcode.OpFOrdLessThan else if (is_signed) Opcode.OpSLessThan else Opcode.OpULessThan,
    791             .cmp_lte => if (is_float) Opcode.OpFOrdLessThanEqual else if (is_signed) Opcode.OpSLessThanEqual else Opcode.OpULessThanEqual,
    792             else => unreachable,
    793         };
    794 
    795         try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });
    796         return result_id;
    797     }
    798 
    799     fn genUnOp(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
    800         const operand_id = try self.resolve(inst.operand);
    801 
    802         const result_id = self.spv.allocResultId();
    803         const result_type_id = try self.genType(inst.base.src, inst.base.ty);
    804 
    805         const info = try self.arithmeticTypeInfo(inst.operand.ty);
    806 
    807         const opcode = switch (inst.base.tag) {
    808             // Bool -> bool
    809             .not => Opcode.OpLogicalNot,
    810             else => unreachable,
    811         };
    812 
    813         try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, operand_id });
    814 
    815         return result_id;
    816     }
    817 
    818     fn genAlloc(self: *DeclGen, inst: *Inst.NoOp) !ResultId {
    819         const storage_class = spec.StorageClass.Function;
    820         const result_type_id = try self.genPointerType(inst.base.src, inst.base.ty, storage_class);
    821         const result_id = self.spv.allocResultId();
    822 
    823         // Rather than generating into code here, we're just going to generate directly into the fn_decls section so that
    824         // variable declarations appear in the first block of the function.
    825         try writeInstruction(&self.spv.binary.fn_decls, .OpVariable, &[_]Word{ result_type_id, result_id, @enumToInt(storage_class) });
    826 
    827         return result_id;
    828     }
    829 
    830     fn genArg(self: *DeclGen) ResultId {
    831         defer self.next_arg_index += 1;
    832         return self.args.items[self.next_arg_index];
    833     }
    834 
    835     fn genBlock(self: *DeclGen, inst: *Inst.Block) !?ResultId {
    836         // In IR, a block doesn't really define an entry point like a block, but more like a scope that breaks can jump out of and
    837         // "return" a value from. This cannot be directly modelled in SPIR-V, so in a block instruction, we're going to split up
    838         // the current block by first generating the code of the block, then a label, and then generate the rest of the current
    839         // ir.Block in a different SPIR-V block.
    840 
    841         const label_id = self.spv.allocResultId();
    842 
    843         // 4 chosen as arbitrary initial capacity.
    844         var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.spv.gpa, 4);
    845 
    846         try self.blocks.putNoClobber(inst, .{
    847             .label_id = label_id,
    848             .incoming_blocks = &incoming_blocks,
    849         });
    850         defer {
    851             assert(self.blocks.remove(inst));
    852             incoming_blocks.deinit(self.spv.gpa);
    853         }
    854 
    855         try self.genBody(inst.body);
    856         try self.beginSPIRVBlock(label_id);
    857 
    858         // If this block didn't produce a value, simply return here.
    859         if (!inst.base.ty.hasCodeGenBits())
    860             return null;
    861 
    862         // Combine the result from the blocks using the Phi instruction.
    863 
    864         const result_id = self.spv.allocResultId();
    865 
    866         // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types
    867         // are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws
    868         // an error for pointers.
    869         const result_type_id = try self.genType(inst.base.src, inst.base.ty);
    870 
    871         try writeOpcode(&self.code, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent...
    872 
    873         for (incoming_blocks.items) |incoming| {
    874             try self.code.appendSlice(&[_]Word{ incoming.break_value_id, incoming.src_label_id });
    875         }
    876 
    877         return result_id;
    878     }
    879 
    880     fn genBr(self: *DeclGen, inst: *Inst.Br) !void {
    881         // TODO: This instruction needs to be the last in a block. Is that guaranteed?
    882         const target = self.blocks.get(inst.block).?;
    883 
    884         // TODO: For some reason, br is emitted with void parameters.
    885         if (inst.operand.ty.hasCodeGenBits()) {
    886             const operand_id = try self.resolve(inst.operand);
    887             // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body.
    888             try target.incoming_blocks.append(self.spv.gpa, .{
    889                 .src_label_id = self.current_block_label_id,
    890                 .break_value_id = operand_id
    891             });
    892         }
    893 
    894         try writeInstruction(&self.code, .OpBranch, &[_]Word{target.label_id});
    895     }
    896 
    897     fn genBrVoid(self: *DeclGen, inst: *Inst.BrVoid) !void {
    898         // TODO: This instruction needs to be the last in a block. Is that guaranteed?
    899         const target = self.blocks.get(inst.block).?;
    900         // Don't need to add this to the incoming block list, as there is no value to insert in the phi node anyway.
    901         try writeInstruction(&self.code, .OpBranch, &[_]Word{target.label_id});
    902     }
    903 
    904     fn genCondBr(self: *DeclGen, inst: *Inst.CondBr) !void {
    905         // TODO: This instruction needs to be the last in a block. Is that guaranteed?
    906         const condition_id = try self.resolve(inst.condition);
    907 
    908         // These will always generate a new SPIR-V block, since they are ir.Body and not ir.Block.
    909         const then_label_id = self.spv.allocResultId();
    910         const else_label_id = self.spv.allocResultId();
    911 
    912         // TODO: We can generate OpSelectionMerge here if we know the target block that both of these will resolve to,
    913         // but i don't know if those will always resolve to the same block.
    914 
    915         try writeInstruction(&self.code, .OpBranchConditional, &[_]Word{
    916             condition_id,
    917             then_label_id,
    918             else_label_id,
    919         });
    920 
    921         try self.beginSPIRVBlock(then_label_id);
    922         try self.genBody(inst.then_body);
    923         try self.beginSPIRVBlock(else_label_id);
    924         try self.genBody(inst.else_body);
    925     }
    926 
    927     fn genDbgStmt(self: *DeclGen, inst: *Inst.DbgStmt) !void {
    928         const src_fname_id = try self.spv.resolveSourceFileName(self.decl);
    929         try writeInstruction(&self.code, .OpLine, &[_]Word{ src_fname_id, inst.line, inst.column });
    930     }
    931 
    932     fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
    933         const operand_id = try self.resolve(inst.operand);
    934 
    935         const result_type_id = try self.genType(inst.base.src, inst.base.ty);
    936         const result_id = self.spv.allocResultId();
    937 
    938         const operands = if (inst.base.ty.isVolatilePtr())
    939             &[_]Word{ result_type_id, result_id, operand_id, @bitCast(u32, spec.MemoryAccess{.Volatile = true}) }
    940         else
    941             &[_]Word{ result_type_id, result_id, operand_id};
    942 
    943         try writeInstruction(&self.code, .OpLoad, operands);
    944 
    945         return result_id;
    946     }
    947 
    948     fn genLoop(self: *DeclGen, inst: *Inst.Loop) !void {
    949         // TODO: This instruction needs to be the last in a block. Is that guaranteed?
    950         const loop_label_id = self.spv.allocResultId();
    951 
    952         // Jump to the loop entry point
    953         try writeInstruction(&self.code, .OpBranch, &[_]Word{ loop_label_id });
    954 
    955         // TODO: Look into OpLoopMerge.
    956 
    957         try self.beginSPIRVBlock(loop_label_id);
    958         try self.genBody(inst.body);
    959 
    960         try writeInstruction(&self.code, .OpBranch, &[_]Word{ loop_label_id });
    961     }
    962 
    963     fn genRet(self: *DeclGen, inst: *Inst.UnOp) !void {
    964         const operand_id = try self.resolve(inst.operand);
    965         // TODO: This instruction needs to be the last in a block. Is that guaranteed?
    966         try writeInstruction(&self.code, .OpReturnValue, &[_]Word{operand_id});
    967     }
    968 
    969     fn genRetVoid(self: *DeclGen) !void {
    970         // TODO: This instruction needs to be the last in a block. Is that guaranteed?
    971         try writeInstruction(&self.code, .OpReturn, &[_]Word{});
    972     }
    973 
    974     fn genStore(self: *DeclGen, inst: *Inst.BinOp) !void {
    975         const dst_ptr_id = try self.resolve(inst.lhs);
    976         const src_val_id = try self.resolve(inst.rhs);
    977 
    978         const operands = if (inst.lhs.ty.isVolatilePtr())
    979             &[_]Word{ dst_ptr_id, src_val_id, @bitCast(u32, spec.MemoryAccess{.Volatile = true}) }
    980         else
    981             &[_]Word{ dst_ptr_id, src_val_id };
    982 
    983         try writeInstruction(&self.code, .OpStore, operands);
    984     }
    985 
    986     fn genUnreach(self: *DeclGen) !void {
    987         // TODO: This instruction needs to be the last in a block. Is that guaranteed?
    988         try writeInstruction(&self.code, .OpUnreachable, &[_]Word{});
    989     }
    990 };