authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-21 02:08:14+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-05-22 16:11:56+02:00
log6634abfd2669a902a86f2c61dbc011310e1f31c4
tree2dbec519f64f983c8a79c85a0d5c744f7bad21bf
parente3be1a1e88bc76d5886122048e44673b692e6db6

SPIR-V: Debug line info/source info


2 files changed, 122 insertions(+), 44 deletions(-)

src/codegen/spirv.zig+98-18
...@@ -40,34 +40,92 @@ pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []cons...@@ -40,34 +40,92 @@ pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []cons
40 try code.appendSlice(args);40 try code.appendSlice(args);
41}41}
4242
43pub fn writeInstructionWithString(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word, str: []const u8) !void {
44 // Str needs to be written zero-terminated, so we need to add one to the length.
45 const zero_terminated_len = str.len + 1;
46 const str_words = (zero_terminated_len + @sizeOf(Word) - 1) / @sizeOf(Word);
47
48 try writeOpcode(code, opcode, @intCast(u16, args.len + str_words));
49 try code.ensureUnusedCapacity(args.len + str_words);
50 code.appendSliceAssumeCapacity(args);
51
52 // TODO: Not actually sure whether this is correct for big-endian.
53 // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal
54 var i: usize = 0;
55 while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
56 var word: Word = 0;
57
58 var j: usize = 0;
59 while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
60 word |= @as(Word, str[i + j]) << @intCast(std.math.Log2Int(Word), j * std.meta.bitCount(u8));
61 }
62
63 code.appendAssumeCapacity(word);
64 }
65}
66
43/// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information.67/// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information.
44/// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's68/// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
45/// of data which needs to be persistent over different calls to Decl code generation.69/// of data which needs to be persistent over different calls to Decl code generation.
46pub const SPIRVModule = struct {70pub const SPIRVModule = struct {
71 /// A general-purpose allocator which may be used to allocate temporary resources required for compilation.
72 gpa: *Allocator,
73
74 /// The parent module.
75 module: *Module,
76
77 /// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these.
47 next_result_id: ResultId,78 next_result_id: ResultId,
4879
80 /// Code of the actual SPIR-V binary, divided into the relevant logical sections.
81 /// Note: To save some bytes, these could also be unmanaged, but since there is only one instance of SPIRVModule
82 /// and this removes some clutter in the rest of the backend, it's fine like this.
49 binary: struct {83 binary: struct {
84 /// OpCapability and OpExtension instructions (in that order).
85 capabilities_and_extensions: std.ArrayList(Word),
86
87 /// OpString, OpSourceExtension, OpSource, OpSourceContinued.
88 debug_strings: std.ArrayList(Word),
89
90 /// Type declaration instructions, constant instructions, global variable declarations, OpUndef instructions.
50 types_globals_constants: std.ArrayList(Word),91 types_globals_constants: std.ArrayList(Word),
92
93 /// Regular functions.
51 fn_decls: std.ArrayList(Word),94 fn_decls: std.ArrayList(Word),
52 },95 },
5396
97 /// Global type cache to reduce the amount of generated types.
54 types: TypeMap,98 types: TypeMap,
5599
56 pub fn init(gpa: *Allocator) SPIRVModule {100 /// Cache for results of OpString instructions for module file names fed to OpSource.
101 /// Since OpString is pretty much only used for those, we don't need to keep track of all strings,
102 /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
103 file_names: std.StringHashMap(ResultId),
104
105 pub fn init(gpa: *Allocator, module: *Module) SPIRVModule {
57 return .{106 return .{
107 .gpa = gpa,
108 .module = module,
58 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.109 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.
59 .binary = .{110 .binary = .{
111 .capabilities_and_extensions = std.ArrayList(Word).init(gpa),
112 .debug_strings = std.ArrayList(Word).init(gpa),
60 .types_globals_constants = std.ArrayList(Word).init(gpa),113 .types_globals_constants = std.ArrayList(Word).init(gpa),
61 .fn_decls = std.ArrayList(Word).init(gpa),114 .fn_decls = std.ArrayList(Word).init(gpa),
62 },115 },
63 .types = TypeMap.init(gpa),116 .types = TypeMap.init(gpa),
117 .file_names = std.StringHashMap(ResultId).init(gpa),
64 };118 };
65 }119 }
66120
67 pub fn deinit(self: *SPIRVModule) void {121 pub fn deinit(self: *SPIRVModule) void {
68 self.binary.types_globals_constants.deinit();122 self.file_names.deinit();
69 self.binary.fn_decls.deinit();
70 self.types.deinit();123 self.types.deinit();
124
125 self.binary.fn_decls.deinit();
126 self.binary.types_globals_constants.deinit();
127 self.binary.debug_strings.deinit();
128 self.binary.capabilities_and_extensions.deinit();
71 }129 }
72130
73 pub fn allocResultId(self: *SPIRVModule) Word {131 pub fn allocResultId(self: *SPIRVModule) Word {
...@@ -78,13 +136,26 @@ pub const SPIRVModule = struct {...@@ -78,13 +136,26 @@ pub const SPIRVModule = struct {
78 pub fn resultIdBound(self: *SPIRVModule) Word {136 pub fn resultIdBound(self: *SPIRVModule) Word {
79 return self.next_result_id;137 return self.next_result_id;
80 }138 }
139
140 fn resolveSourceFileName(self: *SPIRVModule, decl: *Decl) !ResultId {
141 const path = decl.namespace.file_scope.sub_file_path;
142 const result = try self.file_names.getOrPut(path);
143 if (!result.found_existing) {
144 result.entry.value = self.allocResultId();
145 try writeInstructionWithString(&self.binary.debug_strings, .OpString, &[_]Word{result.entry.value}, path);
146 try writeInstruction(&self.binary.debug_strings, .OpSource, &[_]Word{
147 @enumToInt(spec.SourceLanguage.Unknown), // TODO: Register Zig source language.
148 0, // TODO: Zig version as u32?
149 result.entry.value,
150 });
151 }
152
153 return result.entry.value;
154 }
81};155};
82156
83/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.157/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
84pub const DeclGen = struct {158pub const DeclGen = struct {
85 /// The parent module.
86 module: *Module,
87
88 /// The SPIR-V module code should be put in.159 /// The SPIR-V module code should be put in.
89 spv: *SPIRVModule,160 spv: *SPIRVModule,
90161
...@@ -158,9 +229,8 @@ pub const DeclGen = struct {...@@ -158,9 +229,8 @@ pub const DeclGen = struct {
158 };229 };
159230
160 /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, only set when `gen` is called.231 /// Initialize the common resources of a DeclGen. Some fields are left uninitialized, only set when `gen` is called.
161 pub fn init(gpa: *Allocator, module: *Module, spv: *SPIRVModule) DeclGen {232 pub fn init(gpa: *Allocator, spv: *SPIRVModule) DeclGen {
162 return .{233 return .{
163 .module = module,
164 .spv = spv,234 .spv = spv,
165 .args = std.ArrayList(ResultId).init(gpa),235 .args = std.ArrayList(ResultId).init(gpa),
166 .next_arg_index = undefined,236 .next_arg_index = undefined,
...@@ -196,10 +266,14 @@ pub const DeclGen = struct {...@@ -196,10 +266,14 @@ pub const DeclGen = struct {
196 self.blocks.deinit();266 self.blocks.deinit();
197 }267 }
198268
269 fn getTarget(self: *DeclGen) std.Target {
270 return self.spv.module.getTarget();
271 }
272
199 fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {273 fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {
200 @setCold(true);274 @setCold(true);
201 const src_loc = src.toSrcLocWithDecl(self.decl);275 const src_loc = src.toSrcLocWithDecl(self.decl);
202 self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, format, args);276 self.error_msg = try Module.ErrorMsg.create(self.spv.module.gpa, src_loc, format, args);
203 return error.AnalysisFail;277 return error.AnalysisFail;
204 }278 }
205279
...@@ -227,7 +301,7 @@ pub const DeclGen = struct {...@@ -227,7 +301,7 @@ pub const DeclGen = struct {
227 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).301 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
228 /// TODO: Should the result of this function be cached?302 /// TODO: Should the result of this function be cached?
229 fn backingIntBits(self: *DeclGen, bits: u16) ?u16 {303 fn backingIntBits(self: *DeclGen, bits: u16) ?u16 {
230 const target = self.module.getTarget();304 const target = self.getTarget();
231305
232 // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.306 // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.
233 std.debug.assert(bits != 0);307 std.debug.assert(bits != 0);
...@@ -262,7 +336,7 @@ pub const DeclGen = struct {...@@ -262,7 +336,7 @@ pub const DeclGen = struct {
262 /// is no way of knowing whether those are actually supported.336 /// is no way of knowing whether those are actually supported.
263 /// TODO: Maybe this should be cached?337 /// TODO: Maybe this should be cached?
264 fn largestSupportedIntBits(self: *DeclGen) u16 {338 fn largestSupportedIntBits(self: *DeclGen) u16 {
265 const target = self.module.getTarget();339 const target = self.getTarget();
266 return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))340 return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
267 64341 64
268 else342 else
...@@ -277,7 +351,7 @@ pub const DeclGen = struct {...@@ -277,7 +351,7 @@ pub const DeclGen = struct {
277 }351 }
278352
279 fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo {353 fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo {
280 const target = self.module.getTarget();354 const target = self.getTarget();
281 return switch (ty.zigTypeTag()) {355 return switch (ty.zigTypeTag()) {
282 .Bool => ArithmeticTypeInfo{356 .Bool => ArithmeticTypeInfo{
283 .bits = 1, // Doesn't matter for this class.357 .bits = 1, // Doesn't matter for this class.
...@@ -313,7 +387,7 @@ pub const DeclGen = struct {...@@ -313,7 +387,7 @@ pub const DeclGen = struct {
313 /// Generate a constant representing `val`.387 /// Generate a constant representing `val`.
314 /// TODO: Deduplication?388 /// TODO: Deduplication?
315 fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId {389 fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId {
316 const target = self.module.getTarget();390 const target = self.getTarget();
317 const code = &self.spv.binary.types_globals_constants;391 const code = &self.spv.binary.types_globals_constants;
318 const result_id = self.spv.allocResultId();392 const result_id = self.spv.allocResultId();
319 const result_type_id = try self.genType(src, ty);393 const result_type_id = try self.genType(src, ty);
...@@ -398,7 +472,7 @@ pub const DeclGen = struct {...@@ -398,7 +472,7 @@ pub const DeclGen = struct {
398 return already_generated;472 return already_generated;
399 }473 }
400474
401 const target = self.module.getTarget();475 const target = self.getTarget();
402 const code = &self.spv.binary.types_globals_constants;476 const code = &self.spv.binary.types_globals_constants;
403 const result_id = self.spv.allocResultId();477 const result_id = self.spv.allocResultId();
404478
...@@ -587,7 +661,7 @@ pub const DeclGen = struct {...@@ -587,7 +661,7 @@ pub const DeclGen = struct {
587 .breakpoint => null,661 .breakpoint => null,
588 .condbr => try self.genCondBr(inst.castTag(.condbr).?),662 .condbr => try self.genCondBr(inst.castTag(.condbr).?),
589 .constant => unreachable,663 .constant => unreachable,
590 .dbg_stmt => null,664 .dbg_stmt => try self.genDbgStmt(inst.castTag(.dbg_stmt).?),
591 .load => try self.genLoad(inst.castTag(.load).?),665 .load => try self.genLoad(inst.castTag(.load).?),
592 .loop => try self.genLoop(inst.castTag(.loop).?),666 .loop => try self.genLoop(inst.castTag(.loop).?),
593 .ret => try self.genRet(inst.castTag(.ret).?),667 .ret => try self.genRet(inst.castTag(.ret).?),
...@@ -748,7 +822,7 @@ pub const DeclGen = struct {...@@ -748,7 +822,7 @@ pub const DeclGen = struct {
748 const label_id = self.spv.allocResultId();822 const label_id = self.spv.allocResultId();
749823
750 // 4 chosen as arbitrary initial capacity.824 // 4 chosen as arbitrary initial capacity.
751 var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.module.gpa, 4);825 var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.spv.gpa, 4);
752826
753 try self.blocks.putNoClobber(inst, .{827 try self.blocks.putNoClobber(inst, .{
754 .label_id = label_id,828 .label_id = label_id,
...@@ -756,7 +830,7 @@ pub const DeclGen = struct {...@@ -756,7 +830,7 @@ pub const DeclGen = struct {
756 });830 });
757 defer {831 defer {
758 self.blocks.removeAssertDiscard(inst);832 self.blocks.removeAssertDiscard(inst);
759 incoming_blocks.deinit(self.module.gpa);833 incoming_blocks.deinit(self.spv.gpa);
760 }834 }
761835
762 try self.genBody(inst.body);836 try self.genBody(inst.body);
...@@ -792,7 +866,7 @@ pub const DeclGen = struct {...@@ -792,7 +866,7 @@ pub const DeclGen = struct {
792 if (inst.operand.ty.hasCodeGenBits()) {866 if (inst.operand.ty.hasCodeGenBits()) {
793 const operand_id = try self.resolve(inst.operand);867 const operand_id = try self.resolve(inst.operand);
794 // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body.868 // current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body.
795 try target.incoming_blocks.append(self.module.gpa, .{869 try target.incoming_blocks.append(self.spv.gpa, .{
796 .src_label_id = self.current_block_label_id,870 .src_label_id = self.current_block_label_id,
797 .break_value_id = operand_id871 .break_value_id = operand_id
798 });872 });
...@@ -836,6 +910,12 @@ pub const DeclGen = struct {...@@ -836,6 +910,12 @@ pub const DeclGen = struct {
836 return null;910 return null;
837 }911 }
838912
913 fn genDbgStmt(self: *DeclGen, inst: *Inst.DbgStmt) !?ResultId {
914 const src_fname_id = try self.spv.resolveSourceFileName(self.decl);
915 try writeInstruction(&self.spv.binary.fn_decls, .OpLine, &[_]Word{ src_fname_id, inst.line, inst.column });
916 return null;
917 }
918
839 fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId {919 fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
840 const operand_id = try self.resolve(inst.operand);920 const operand_id = try self.resolve(inst.operand);
841921
src/link/SpirV.zig+24-26
...@@ -132,7 +132,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -132,7 +132,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
132 const module = self.base.options.module.?;132 const module = self.base.options.module.?;
133 const target = comp.getTarget();133 const target = comp.getTarget();
134134
135 var spv = codegen.SPIRVModule.init(self.base.allocator);135 var spv = codegen.SPIRVModule.init(self.base.allocator, module);
136 defer spv.deinit();136 defer spv.deinit();
137137
138 // Allocate an ID for every declaration before generating code,138 // Allocate an ID for every declaration before generating code,
...@@ -152,7 +152,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -152,7 +152,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
152152
153 // Now, actually generate the code for all declarations.153 // Now, actually generate the code for all declarations.
154 {154 {
155 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv);155 var decl_gen = codegen.DeclGen.init(self.base.allocator, &spv);
156 defer decl_gen.deinit();156 defer decl_gen.deinit();
157157
158 for (self.decl_table.items()) |entry| {158 for (self.decl_table.items()) |entry| {
...@@ -166,39 +166,45 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {...@@ -166,39 +166,45 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
166 }166 }
167 }167 }
168168
169 var binary = std.ArrayList(Word).init(self.base.allocator);169 try writeCapabilities(&spv.binary.capabilities_and_extensions, target);
170 defer binary.deinit();170 try writeMemoryModel(&spv.binary.capabilities_and_extensions, target);
171171
172 try binary.appendSlice(&[_]Word{172 const header = [_]Word{
173 spec.magic_number,173 spec.magic_number,
174 (spec.version.major << 16) | (spec.version.minor << 8),174 (spec.version.major << 16) | (spec.version.minor << 8),
175 0, // TODO: Register Zig compiler magic number.175 0, // TODO: Register Zig compiler magic number.
176 spv.resultIdBound(), // ID bound.176 spv.resultIdBound(),
177 0, // Schema (currently reserved for future use in the SPIR-V spec).177 0, // Schema (currently reserved for future use in the SPIR-V spec).
178 });178 };
179
180 try writeCapabilities(&binary, target);
181 try writeMemoryModel(&binary, target);
182179
183 // Note: The order of adding sections to the final binary180 // Note: The order of adding sections to the final binary
184 // follows the SPIR-V logical module format!181 // follows the SPIR-V logical module format!
185 var all_buffers = [_]std.os.iovec_const{182 const buffers = &[_][]const Word{
186 wordsToIovConst(binary.items),183 &header,
187 wordsToIovConst(spv.binary.types_globals_constants.items),184 spv.binary.capabilities_and_extensions.items,
188 wordsToIovConst(spv.binary.fn_decls.items),185 spv.binary.debug_strings.items,
186 spv.binary.types_globals_constants.items,
187 spv.binary.fn_decls.items,
189 };188 };
190189
191 const file = self.base.file.?;190 var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;
192 const bytes = std.mem.sliceAsBytes(binary.items);191 for (iovc_buffers) |*iovc, i| {
192 const bytes = std.mem.sliceAsBytes(buffers[i]);
193 iovc.* = .{
194 .iov_base = bytes.ptr,
195 .iov_len = bytes.len
196 };
197 }
193198
194 var file_size: u64 = 0;199 var file_size: u64 = 0;
195 for (all_buffers) |iov| {200 for (iovc_buffers) |iov| {
196 file_size += iov.iov_len;201 file_size += iov.iov_len;
197 }202 }
198203
204 const file = self.base.file.?;
199 try file.seekTo(0);205 try file.seekTo(0);
200 try file.setEndPos(file_size);206 try file.setEndPos(file_size);
201 try file.pwritevAll(&all_buffers, 0);207 try file.pwritevAll(&iovc_buffers, 0);
202}208}
203209
204fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {210fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {
...@@ -235,11 +241,3 @@ fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {...@@ -235,11 +241,3 @@ fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {
235 @enumToInt(addressing_model), @enumToInt(memory_model),241 @enumToInt(addressing_model), @enumToInt(memory_model),
236 });242 });
237}243}
238
239fn wordsToIovConst(words: []const Word) std.os.iovec_const {
240 const bytes = std.mem.sliceAsBytes(words);
241 return .{
242 .iov_base = bytes.ptr,
243 .iov_len = bytes.len,
244 };
245}