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
4040 try code.appendSlice(args);
4141}
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
4367/// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information.
4468/// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
4569/// of data which needs to be persistent over different calls to Decl code generation.
4670pub 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.
4778 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.
4983 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.
5091 types_globals_constants: std.ArrayList(Word),
92
93 /// Regular functions.
5194 fn_decls: std.ArrayList(Word),
5295 },
5396
97 /// Global type cache to reduce the amount of generated types.
5498 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 {
57106 return .{
107 .gpa = gpa,
108 .module = module,
58109 .next_result_id = 1, // 0 is an invalid SPIR-V result ID.
59110 .binary = .{
111 .capabilities_and_extensions = std.ArrayList(Word).init(gpa),
112 .debug_strings = std.ArrayList(Word).init(gpa),
60113 .types_globals_constants = std.ArrayList(Word).init(gpa),
61114 .fn_decls = std.ArrayList(Word).init(gpa),
62115 },
63116 .types = TypeMap.init(gpa),
117 .file_names = std.StringHashMap(ResultId).init(gpa),
64118 };
65119 }
66120
67121 pub fn deinit(self: *SPIRVModule) void {
68 self.binary.types_globals_constants.deinit();
69 self.binary.fn_decls.deinit();
122 self.file_names.deinit();
70123 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();
71129 }
72130
73131 pub fn allocResultId(self: *SPIRVModule) Word {
......@@ -78,13 +136,26 @@ pub const SPIRVModule = struct {
78136 pub fn resultIdBound(self: *SPIRVModule) Word {
79137 return self.next_result_id;
80138 }
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 }
81155};
82156
83157/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
84158pub const DeclGen = struct {
85 /// The parent module.
86 module: *Module,
87
88159 /// The SPIR-V module code should be put in.
89160 spv: *SPIRVModule,
90161
......@@ -158,9 +229,8 @@ pub const DeclGen = struct {
158229 };
159230
160231 /// 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 {
162233 return .{
163 .module = module,
164234 .spv = spv,
165235 .args = std.ArrayList(ResultId).init(gpa),
166236 .next_arg_index = undefined,
......@@ -196,10 +266,14 @@ pub const DeclGen = struct {
196266 self.blocks.deinit();
197267 }
198268
269 fn getTarget(self: *DeclGen) std.Target {
270 return self.spv.module.getTarget();
271 }
272
199273 fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {
200274 @setCold(true);
201275 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);
203277 return error.AnalysisFail;
204278 }
205279
......@@ -227,7 +301,7 @@ pub const DeclGen = struct {
227301 /// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
228302 /// TODO: Should the result of this function be cached?
229303 fn backingIntBits(self: *DeclGen, bits: u16) ?u16 {
230 const target = self.module.getTarget();
304 const target = self.getTarget();
231305
232306 // The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.
233307 std.debug.assert(bits != 0);
......@@ -262,7 +336,7 @@ pub const DeclGen = struct {
262336 /// is no way of knowing whether those are actually supported.
263337 /// TODO: Maybe this should be cached?
264338 fn largestSupportedIntBits(self: *DeclGen) u16 {
265 const target = self.module.getTarget();
339 const target = self.getTarget();
266340 return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
267341 64
268342 else
......@@ -277,7 +351,7 @@ pub const DeclGen = struct {
277351 }
278352
279353 fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo {
280 const target = self.module.getTarget();
354 const target = self.getTarget();
281355 return switch (ty.zigTypeTag()) {
282356 .Bool => ArithmeticTypeInfo{
283357 .bits = 1, // Doesn't matter for this class.
......@@ -313,7 +387,7 @@ pub const DeclGen = struct {
313387 /// Generate a constant representing `val`.
314388 /// TODO: Deduplication?
315389 fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId {
316 const target = self.module.getTarget();
390 const target = self.getTarget();
317391 const code = &self.spv.binary.types_globals_constants;
318392 const result_id = self.spv.allocResultId();
319393 const result_type_id = try self.genType(src, ty);
......@@ -398,7 +472,7 @@ pub const DeclGen = struct {
398472 return already_generated;
399473 }
400474
401 const target = self.module.getTarget();
475 const target = self.getTarget();
402476 const code = &self.spv.binary.types_globals_constants;
403477 const result_id = self.spv.allocResultId();
404478
......@@ -587,7 +661,7 @@ pub const DeclGen = struct {
587661 .breakpoint => null,
588662 .condbr => try self.genCondBr(inst.castTag(.condbr).?),
589663 .constant => unreachable,
590 .dbg_stmt => null,
664 .dbg_stmt => try self.genDbgStmt(inst.castTag(.dbg_stmt).?),
591665 .load => try self.genLoad(inst.castTag(.load).?),
592666 .loop => try self.genLoop(inst.castTag(.loop).?),
593667 .ret => try self.genRet(inst.castTag(.ret).?),
......@@ -748,7 +822,7 @@ pub const DeclGen = struct {
748822 const label_id = self.spv.allocResultId();
749823
750824 // 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
753827 try self.blocks.putNoClobber(inst, .{
754828 .label_id = label_id,
......@@ -756,7 +830,7 @@ pub const DeclGen = struct {
756830 });
757831 defer {
758832 self.blocks.removeAssertDiscard(inst);
759 incoming_blocks.deinit(self.module.gpa);
833 incoming_blocks.deinit(self.spv.gpa);
760834 }
761835
762836 try self.genBody(inst.body);
......@@ -792,7 +866,7 @@ pub const DeclGen = struct {
792866 if (inst.operand.ty.hasCodeGenBits()) {
793867 const operand_id = try self.resolve(inst.operand);
794868 // 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, .{
796870 .src_label_id = self.current_block_label_id,
797871 .break_value_id = operand_id
798872 });
......@@ -836,6 +910,12 @@ pub const DeclGen = struct {
836910 return null;
837911 }
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
839919 fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
840920 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 {
132132 const module = self.base.options.module.?;
133133 const target = comp.getTarget();
134134
135 var spv = codegen.SPIRVModule.init(self.base.allocator);
135 var spv = codegen.SPIRVModule.init(self.base.allocator, module);
136136 defer spv.deinit();
137137
138138 // Allocate an ID for every declaration before generating code,
......@@ -152,7 +152,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
152152
153153 // Now, actually generate the code for all declarations.
154154 {
155 var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv);
155 var decl_gen = codegen.DeclGen.init(self.base.allocator, &spv);
156156 defer decl_gen.deinit();
157157
158158 for (self.decl_table.items()) |entry| {
......@@ -166,39 +166,45 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
166166 }
167167 }
168168
169 var binary = std.ArrayList(Word).init(self.base.allocator);
170 defer binary.deinit();
169 try writeCapabilities(&spv.binary.capabilities_and_extensions, target);
170 try writeMemoryModel(&spv.binary.capabilities_and_extensions, target);
171171
172 try binary.appendSlice(&[_]Word{
172 const header = [_]Word{
173173 spec.magic_number,
174174 (spec.version.major << 16) | (spec.version.minor << 8),
175175 0, // TODO: Register Zig compiler magic number.
176 spv.resultIdBound(), // ID bound.
176 spv.resultIdBound(),
177177 0, // Schema (currently reserved for future use in the SPIR-V spec).
178 });
179
180 try writeCapabilities(&binary, target);
181 try writeMemoryModel(&binary, target);
178 };
182179
183180 // Note: The order of adding sections to the final binary
184181 // follows the SPIR-V logical module format!
185 var all_buffers = [_]std.os.iovec_const{
186 wordsToIovConst(binary.items),
187 wordsToIovConst(spv.binary.types_globals_constants.items),
188 wordsToIovConst(spv.binary.fn_decls.items),
182 const buffers = &[_][]const Word{
183 &header,
184 spv.binary.capabilities_and_extensions.items,
185 spv.binary.debug_strings.items,
186 spv.binary.types_globals_constants.items,
187 spv.binary.fn_decls.items,
189188 };
190189
191 const file = self.base.file.?;
192 const bytes = std.mem.sliceAsBytes(binary.items);
190 var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;
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
194199 var file_size: u64 = 0;
195 for (all_buffers) |iov| {
200 for (iovc_buffers) |iov| {
196201 file_size += iov.iov_len;
197202 }
198203
204 const file = self.base.file.?;
199205 try file.seekTo(0);
200206 try file.setEndPos(file_size);
201 try file.pwritevAll(&all_buffers, 0);
207 try file.pwritevAll(&iovc_buffers, 0);
202208}
203209
204210fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {
......@@ -235,11 +241,3 @@ fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {
235241 @enumToInt(addressing_model), @enumToInt(memory_model),
236242 });
237243}
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}