aboutsummaryrefslogtreecommitdiff
path: root/src/codegen
diff options
context:
space:
mode:
authorRobin Voetter <robin@voetter.nl>2021-05-21 02:08:14 +0200
committerRobin Voetter <robin@voetter.nl>2021-05-22 16:11:56 +0200
commit6634abfd2669a902a86f2c61dbc011310e1f31c4 (patch)
tree2dbec519f64f983c8a79c85a0d5c744f7bad21bf /src/codegen
parente3be1a1e88bc76d5886122048e44673b692e6db6 (diff)
downloadzig-6634abfd2669a902a86f2c61dbc011310e1f31c4.tar.gz
zig-6634abfd2669a902a86f2c61dbc011310e1f31c4.zip
SPIR-V: Debug line info/source info
Diffstat (limited to 'src/codegen')
-rw-r--r--src/codegen/spirv.zig116
1 files changed, 98 insertions, 18 deletions
diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig
index 6687ffd970..e9b4c7c384 100644
--- a/src/codegen/spirv.zig
+++ b/src/codegen/spirv.zig
@@ -40,34 +40,92 @@ pub fn writeInstruction(code: *std.ArrayList(Word), opcode: Opcode, args: []cons
try code.appendSlice(args);
}
+pub fn writeInstructionWithString(code: *std.ArrayList(Word), opcode: Opcode, args: []const Word, str: []const u8) !void {
+ // Str needs to be written zero-terminated, so we need to add one to the length.
+ const zero_terminated_len = str.len + 1;
+ const str_words = (zero_terminated_len + @sizeOf(Word) - 1) / @sizeOf(Word);
+
+ try writeOpcode(code, opcode, @intCast(u16, args.len + str_words));
+ try code.ensureUnusedCapacity(args.len + str_words);
+ code.appendSliceAssumeCapacity(args);
+
+ // TODO: Not actually sure whether this is correct for big-endian.
+ // See https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Literal
+ var i: usize = 0;
+ while (i < zero_terminated_len) : (i += @sizeOf(Word)) {
+ var word: Word = 0;
+
+ var j: usize = 0;
+ while (j < @sizeOf(Word) and i + j < str.len) : (j += 1) {
+ word |= @as(Word, str[i + j]) << @intCast(std.math.Log2Int(Word), j * std.meta.bitCount(u8));
+ }
+
+ code.appendAssumeCapacity(word);
+ }
+}
+
/// This structure represents a SPIR-V (binary) module being compiled, and keeps track of all relevant information.
/// That includes the actual instructions, the current result-id bound, and data structures for querying result-id's
/// of data which needs to be persistent over different calls to Decl code generation.
pub const SPIRVModule = struct {
+ /// A general-purpose allocator which may be used to allocate temporary resources required for compilation.
+ gpa: *Allocator,
+
+ /// The parent module.
+ module: *Module,
+
+ /// SPIR-V instructions return result-ids. This variable holds the module-wide counter for these.
next_result_id: ResultId,
+ /// Code of the actual SPIR-V binary, divided into the relevant logical sections.
+ /// Note: To save some bytes, these could also be unmanaged, but since there is only one instance of SPIRVModule
+ /// and this removes some clutter in the rest of the backend, it's fine like this.
binary: struct {
+ /// OpCapability and OpExtension instructions (in that order).
+ capabilities_and_extensions: std.ArrayList(Word),
+
+ /// OpString, OpSourceExtension, OpSource, OpSourceContinued.
+ debug_strings: std.ArrayList(Word),
+
+ /// Type declaration instructions, constant instructions, global variable declarations, OpUndef instructions.
types_globals_constants: std.ArrayList(Word),
+
+ /// Regular functions.
fn_decls: std.ArrayList(Word),
},
+ /// Global type cache to reduce the amount of generated types.
types: TypeMap,
- pub fn init(gpa: *Allocator) SPIRVModule {
+ /// Cache for results of OpString instructions for module file names fed to OpSource.
+ /// Since OpString is pretty much only used for those, we don't need to keep track of all strings,
+ /// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
+ file_names: std.StringHashMap(ResultId),
+
+ pub fn init(gpa: *Allocator, module: *Module) SPIRVModule {
return .{
+ .gpa = gpa,
+ .module = module,
.next_result_id = 1, // 0 is an invalid SPIR-V result ID.
.binary = .{
+ .capabilities_and_extensions = std.ArrayList(Word).init(gpa),
+ .debug_strings = std.ArrayList(Word).init(gpa),
.types_globals_constants = std.ArrayList(Word).init(gpa),
.fn_decls = std.ArrayList(Word).init(gpa),
},
.types = TypeMap.init(gpa),
+ .file_names = std.StringHashMap(ResultId).init(gpa),
};
}
pub fn deinit(self: *SPIRVModule) void {
- self.binary.types_globals_constants.deinit();
- self.binary.fn_decls.deinit();
+ self.file_names.deinit();
self.types.deinit();
+
+ self.binary.fn_decls.deinit();
+ self.binary.types_globals_constants.deinit();
+ self.binary.debug_strings.deinit();
+ self.binary.capabilities_and_extensions.deinit();
}
pub fn allocResultId(self: *SPIRVModule) Word {
@@ -78,13 +136,26 @@ pub const SPIRVModule = struct {
pub fn resultIdBound(self: *SPIRVModule) Word {
return self.next_result_id;
}
+
+ fn resolveSourceFileName(self: *SPIRVModule, decl: *Decl) !ResultId {
+ const path = decl.namespace.file_scope.sub_file_path;
+ const result = try self.file_names.getOrPut(path);
+ if (!result.found_existing) {
+ result.entry.value = self.allocResultId();
+ try writeInstructionWithString(&self.binary.debug_strings, .OpString, &[_]Word{result.entry.value}, path);
+ try writeInstruction(&self.binary.debug_strings, .OpSource, &[_]Word{
+ @enumToInt(spec.SourceLanguage.Unknown), // TODO: Register Zig source language.
+ 0, // TODO: Zig version as u32?
+ result.entry.value,
+ });
+ }
+
+ return result.entry.value;
+ }
};
/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
pub const DeclGen = struct {
- /// The parent module.
- module: *Module,
-
/// The SPIR-V module code should be put in.
spv: *SPIRVModule,
@@ -158,9 +229,8 @@ pub const DeclGen = struct {
};
/// Initialize the common resources of a DeclGen. Some fields are left uninitialized, only set when `gen` is called.
- pub fn init(gpa: *Allocator, module: *Module, spv: *SPIRVModule) DeclGen {
+ pub fn init(gpa: *Allocator, spv: *SPIRVModule) DeclGen {
return .{
- .module = module,
.spv = spv,
.args = std.ArrayList(ResultId).init(gpa),
.next_arg_index = undefined,
@@ -196,10 +266,14 @@ pub const DeclGen = struct {
self.blocks.deinit();
}
+ fn getTarget(self: *DeclGen) std.Target {
+ return self.spv.module.getTarget();
+ }
+
fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {
@setCold(true);
const src_loc = src.toSrcLocWithDecl(self.decl);
- self.error_msg = try Module.ErrorMsg.create(self.module.gpa, src_loc, format, args);
+ self.error_msg = try Module.ErrorMsg.create(self.spv.module.gpa, src_loc, format, args);
return error.AnalysisFail;
}
@@ -227,7 +301,7 @@ pub const DeclGen = struct {
/// TODO: This probably needs an ABI-version as well (especially in combination with SPV_INTEL_arbitrary_precision_integers).
/// TODO: Should the result of this function be cached?
fn backingIntBits(self: *DeclGen, bits: u16) ?u16 {
- const target = self.module.getTarget();
+ const target = self.getTarget();
// The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.
std.debug.assert(bits != 0);
@@ -262,7 +336,7 @@ pub const DeclGen = struct {
/// is no way of knowing whether those are actually supported.
/// TODO: Maybe this should be cached?
fn largestSupportedIntBits(self: *DeclGen) u16 {
- const target = self.module.getTarget();
+ const target = self.getTarget();
return if (Target.spirv.featureSetHas(target.cpu.features, .Int64))
64
else
@@ -277,7 +351,7 @@ pub const DeclGen = struct {
}
fn arithmeticTypeInfo(self: *DeclGen, ty: Type) !ArithmeticTypeInfo {
- const target = self.module.getTarget();
+ const target = self.getTarget();
return switch (ty.zigTypeTag()) {
.Bool => ArithmeticTypeInfo{
.bits = 1, // Doesn't matter for this class.
@@ -313,7 +387,7 @@ pub const DeclGen = struct {
/// Generate a constant representing `val`.
/// TODO: Deduplication?
fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId {
- const target = self.module.getTarget();
+ const target = self.getTarget();
const code = &self.spv.binary.types_globals_constants;
const result_id = self.spv.allocResultId();
const result_type_id = try self.genType(src, ty);
@@ -398,7 +472,7 @@ pub const DeclGen = struct {
return already_generated;
}
- const target = self.module.getTarget();
+ const target = self.getTarget();
const code = &self.spv.binary.types_globals_constants;
const result_id = self.spv.allocResultId();
@@ -587,7 +661,7 @@ pub const DeclGen = struct {
.breakpoint => null,
.condbr => try self.genCondBr(inst.castTag(.condbr).?),
.constant => unreachable,
- .dbg_stmt => null,
+ .dbg_stmt => try self.genDbgStmt(inst.castTag(.dbg_stmt).?),
.load => try self.genLoad(inst.castTag(.load).?),
.loop => try self.genLoop(inst.castTag(.loop).?),
.ret => try self.genRet(inst.castTag(.ret).?),
@@ -748,7 +822,7 @@ pub const DeclGen = struct {
const label_id = self.spv.allocResultId();
// 4 chosen as arbitrary initial capacity.
- var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.module.gpa, 4);
+ var incoming_blocks = try std.ArrayListUnmanaged(IncomingBlock).initCapacity(self.spv.gpa, 4);
try self.blocks.putNoClobber(inst, .{
.label_id = label_id,
@@ -756,7 +830,7 @@ pub const DeclGen = struct {
});
defer {
self.blocks.removeAssertDiscard(inst);
- incoming_blocks.deinit(self.module.gpa);
+ incoming_blocks.deinit(self.spv.gpa);
}
try self.genBody(inst.body);
@@ -792,7 +866,7 @@ pub const DeclGen = struct {
if (inst.operand.ty.hasCodeGenBits()) {
const operand_id = try self.resolve(inst.operand);
// current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body.
- try target.incoming_blocks.append(self.module.gpa, .{
+ try target.incoming_blocks.append(self.spv.gpa, .{
.src_label_id = self.current_block_label_id,
.break_value_id = operand_id
});
@@ -836,6 +910,12 @@ pub const DeclGen = struct {
return null;
}
+ fn genDbgStmt(self: *DeclGen, inst: *Inst.DbgStmt) !?ResultId {
+ const src_fname_id = try self.spv.resolveSourceFileName(self.decl);
+ try writeInstruction(&self.spv.binary.fn_decls, .OpLine, &[_]Word{ src_fname_id, inst.line, inst.column });
+ return null;
+ }
+
fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
const operand_id = try self.resolve(inst.operand);