| ... | ... | @@ -12,7 +12,23 @@ const trace = @import("../tracy.zig").trace; |
| 12 | 12 | const build_options = @import("build_options"); |
| 13 | 13 | const spec = @import("../codegen/spirv/spec.zig"); |
| 14 | 14 | |
| 15 | | //! SPIR-V Documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html |
| 15 | //! SPIR-V Spec documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html |
| 16 | //! According to above documentation, a SPIR-V module has the following logical layout: |
| 17 | //! Header. |
| 18 | //! OpCapability instructions. |
| 19 | //! OpExtension instructions. |
| 20 | //! OpExtInstImport instructions. |
| 21 | //! A single OpMemoryModel instruction. |
| 22 | //! All entry points, declared with OpEntryPoint instructions. |
| 23 | //! All execution-mode declarators; OpExecutionMode and OpExecutionModeId instructions. |
| 24 | //! Debug instructions: |
| 25 | //! - First, OpString, OpSourceExtension, OpSource, OpSourceContinued (no forward references). |
| 26 | //! - OpName and OpMemberName instructions. |
| 27 | //! - OpModuleProcessed instructions. |
| 28 | //! All annotation (decoration) instructions. |
| 29 | //! All type declaration instructions, constant instructions, global variable declarations, (preferrably) OpUndef instructions. |
| 30 | //! All function declarations without a body (extern functions presumably). |
| 31 | //! All regular functions. |
| 16 | 32 | |
| 17 | 33 | pub const FnData = struct { |
| 18 | 34 | id: ?u32 = null, |
| ... | ... | @@ -103,7 +119,6 @@ pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void { |
| 103 | 119 | decl.fn_link.spirv.code.deinit(self.base.allocator); |
| 104 | 120 | decl.fn_link.spirv = undefined; |
| 105 | 121 | } |
| 106 | | |
| 107 | 122 | pub fn flush(self: *SpirV, comp: *Compilation) !void { |
| 108 | 123 | if (build_options.have_llvm and self.base.options.use_lld) { |
| 109 | 124 | return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. |
| ... | ... | @@ -118,34 +133,60 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { |
| 118 | 133 | |
| 119 | 134 | const module = self.base.options.module.?; |
| 120 | 135 | |
| 121 | | const file = self.base.file.?; |
| 122 | | var bw = std.io.bufferedWriter(file.writer()); |
| 123 | | const writer = bw.writer(); |
| 136 | var binary = std.ArrayList(u32).init(self.base.allocator); |
| 137 | defer binary.deinit(); |
| 124 | 138 | |
| 125 | 139 | // Header |
| 126 | | // SPIR-V files support both little and big endian words. The actual format is disambiguated by |
| 127 | | // the magic number. This backend uses little endian. |
| 128 | | try writer.writeIntLittle(u32, spec.magic_number); |
| 129 | | try writer.writeIntLittle(u32, (spec.version.major << 16) | (spec.version.minor) << 8); |
| 130 | | try writer.writeIntLittle(u32, 0); // TODO: Register Zig compiler magic number. |
| 131 | | try writer.writeIntLittle(u32, self.spirv_module.idBound()); |
| 132 | | try writer.writeIntLittle(u32, 0); // Schema. |
| 133 | | |
| 134 | | // Declarations |
| 140 | { |
| 141 | const header = [_]u32{ |
| 142 | spec.magic_number, |
| 143 | (spec.version.major << 16) | (spec.version.minor << 8), |
| 144 | 0, // TODO: Register Zig compiler magic number. |
| 145 | self.spirv_module.idBound(), |
| 146 | 0, // Schema (currently reserved for future use in the SPIR-V spec). |
| 147 | }; |
| 148 | try binary.appendSlice(&header); |
| 149 | } |
| 150 | |
| 151 | // Collect list of buffers to write. |
| 152 | // SPIR-V files support both little and big endian words. The actual format is |
| 153 | // disambiguated by the magic number, and so theoretically we don't need to worry |
| 154 | // about endian-ness when writing the final binary. |
| 155 | var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator); |
| 156 | defer all_buffers.deinit(); |
| 157 | |
| 158 | // Pre-allocate enough for the binary info + all functions |
| 159 | try all_buffers.ensureCapacity(module.decl_table.count() + 1); |
| 160 | |
| 161 | all_buffers.appendAssumeCapacity(wordsToIovConst(binary.items)); |
| 162 | |
| 163 | // Functions |
| 135 | 164 | for (module.decl_table.items()) |entry| { |
| 136 | 165 | const decl = entry.value; |
| 137 | 166 | switch (decl.typed_value) { |
| 138 | 167 | .most_recent => |tvm| { |
| 139 | 168 | const fn_data = &decl.fn_link.spirv; |
| 140 | | |
| 141 | | // TODO: This could probably be more efficient. |
| 142 | | for (fn_data.code.items) |word| { |
| 143 | | try writer.writeIntLittle(u32, word); |
| 144 | | } |
| 169 | all_buffers.appendAssumeCapacity(wordsToIovConst(fn_data.code.items)); |
| 145 | 170 | }, |
| 146 | 171 | .never_succeeded => continue, |
| 147 | 172 | } |
| 148 | 173 | } |
| 149 | 174 | |
| 150 | | try bw.flush(); |
| 175 | var file_size: u64 = 0; |
| 176 | for (all_buffers.items) |iov| { |
| 177 | file_size += iov.iov_len; |
| 178 | } |
| 179 | |
| 180 | const file = self.base.file.?; |
| 181 | try file.seekTo(0); |
| 182 | try file.setEndPos(file_size); |
| 183 | try file.pwritevAll(all_buffers.items, 0); |
| 184 | } |
| 185 | |
| 186 | fn wordsToIovConst(words: []const u32) std.os.iovec_const { |
| 187 | const bytes = std.mem.sliceAsBytes(words); |
| 188 | return .{ |
| 189 | .iov_base = bytes.ptr, |
| 190 | .iov_len = bytes.len, |
| 191 | }; |
| 151 | 192 | } |