| ... | @@ -12,7 +12,23 @@ const trace = @import("../tracy.zig").trace; | ... | @@ -12,7 +12,23 @@ const trace = @import("../tracy.zig").trace; |
| 12 | const build_options = @import("build_options"); | 12 | const build_options = @import("build_options"); |
| 13 | const spec = @import("../codegen/spirv/spec.zig"); | 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 | pub const FnData = struct { | 33 | pub const FnData = struct { |
| 18 | id: ?u32 = null, | 34 | id: ?u32 = null, |
| ... | @@ -103,7 +119,6 @@ pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void { | ... | @@ -103,7 +119,6 @@ pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void { |
| 103 | decl.fn_link.spirv.code.deinit(self.base.allocator); | 119 | decl.fn_link.spirv.code.deinit(self.base.allocator); |
| 104 | decl.fn_link.spirv = undefined; | 120 | decl.fn_link.spirv = undefined; |
| 105 | } | 121 | } |
| 106 | | | |
| 107 | pub fn flush(self: *SpirV, comp: *Compilation) !void { | 122 | pub fn flush(self: *SpirV, comp: *Compilation) !void { |
| 108 | if (build_options.have_llvm and self.base.options.use_lld) { | 123 | if (build_options.have_llvm and self.base.options.use_lld) { |
| 109 | return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. | 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,34 +133,60 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { |
| 118 | | 133 | |
| 119 | const module = self.base.options.module.?; | 134 | const module = self.base.options.module.?; |
| 120 | | 135 | |
| 121 | const file = self.base.file.?; | 136 | var binary = std.ArrayList(u32).init(self.base.allocator); |
| 122 | var bw = std.io.bufferedWriter(file.writer()); | 137 | defer binary.deinit(); |
| 123 | const writer = bw.writer(); | | |
| 124 | | 138 | |
| 125 | // Header | 139 | // Header |
| 126 | // SPIR-V files support both little and big endian words. The actual format is disambiguated by | 140 | { |
| 127 | // the magic number. This backend uses little endian. | 141 | const header = [_]u32{ |
| 128 | try writer.writeIntLittle(u32, spec.magic_number); | 142 | spec.magic_number, |
| 129 | try writer.writeIntLittle(u32, (spec.version.major << 16) | (spec.version.minor) << 8); | 143 | (spec.version.major << 16) | (spec.version.minor << 8), |
| 130 | try writer.writeIntLittle(u32, 0); // TODO: Register Zig compiler magic number. | 144 | 0, // TODO: Register Zig compiler magic number. |
| 131 | try writer.writeIntLittle(u32, self.spirv_module.idBound()); | 145 | self.spirv_module.idBound(), |
| 132 | try writer.writeIntLittle(u32, 0); // Schema. | 146 | 0, // Schema (currently reserved for future use in the SPIR-V spec). |
| 133 | | 147 | }; |
| 134 | // Declarations | 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 | for (module.decl_table.items()) |entry| { | 164 | for (module.decl_table.items()) |entry| { |
| 136 | const decl = entry.value; | 165 | const decl = entry.value; |
| 137 | switch (decl.typed_value) { | 166 | switch (decl.typed_value) { |
| 138 | .most_recent => |tvm| { | 167 | .most_recent => |tvm| { |
| 139 | const fn_data = &decl.fn_link.spirv; | 168 | const fn_data = &decl.fn_link.spirv; |
| 140 | | 169 | all_buffers.appendAssumeCapacity(wordsToIovConst(fn_data.code.items)); |
| 141 | // TODO: This could probably be more efficient. | | |
| 142 | for (fn_data.code.items) |word| { | | |
| 143 | try writer.writeIntLittle(u32, word); | | |
| 144 | } | | |
| 145 | }, | 170 | }, |
| 146 | .never_succeeded => continue, | 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 | } |