mirror of
https://github.com/ziglang/zig.git
synced 2026-02-17 14:59:14 +00:00
SPIR-V: Debug line info/source info
This commit is contained in:
parent
e3be1a1e88
commit
6634abfd26
@ -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);
|
||||
|
||||
|
||||
@ -132,7 +132,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
|
||||
const module = self.base.options.module.?;
|
||||
const target = comp.getTarget();
|
||||
|
||||
var spv = codegen.SPIRVModule.init(self.base.allocator);
|
||||
var spv = codegen.SPIRVModule.init(self.base.allocator, module);
|
||||
defer spv.deinit();
|
||||
|
||||
// Allocate an ID for every declaration before generating code,
|
||||
@ -152,7 +152,7 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
|
||||
|
||||
// Now, actually generate the code for all declarations.
|
||||
{
|
||||
var decl_gen = codegen.DeclGen.init(self.base.allocator, module, &spv);
|
||||
var decl_gen = codegen.DeclGen.init(self.base.allocator, &spv);
|
||||
defer decl_gen.deinit();
|
||||
|
||||
for (self.decl_table.items()) |entry| {
|
||||
@ -166,39 +166,45 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
|
||||
}
|
||||
}
|
||||
|
||||
var binary = std.ArrayList(Word).init(self.base.allocator);
|
||||
defer binary.deinit();
|
||||
try writeCapabilities(&spv.binary.capabilities_and_extensions, target);
|
||||
try writeMemoryModel(&spv.binary.capabilities_and_extensions, target);
|
||||
|
||||
try binary.appendSlice(&[_]Word{
|
||||
const header = [_]Word{
|
||||
spec.magic_number,
|
||||
(spec.version.major << 16) | (spec.version.minor << 8),
|
||||
0, // TODO: Register Zig compiler magic number.
|
||||
spv.resultIdBound(), // ID bound.
|
||||
spv.resultIdBound(),
|
||||
0, // Schema (currently reserved for future use in the SPIR-V spec).
|
||||
});
|
||||
|
||||
try writeCapabilities(&binary, target);
|
||||
try writeMemoryModel(&binary, target);
|
||||
};
|
||||
|
||||
// Note: The order of adding sections to the final binary
|
||||
// follows the SPIR-V logical module format!
|
||||
var all_buffers = [_]std.os.iovec_const{
|
||||
wordsToIovConst(binary.items),
|
||||
wordsToIovConst(spv.binary.types_globals_constants.items),
|
||||
wordsToIovConst(spv.binary.fn_decls.items),
|
||||
const buffers = &[_][]const Word{
|
||||
&header,
|
||||
spv.binary.capabilities_and_extensions.items,
|
||||
spv.binary.debug_strings.items,
|
||||
spv.binary.types_globals_constants.items,
|
||||
spv.binary.fn_decls.items,
|
||||
};
|
||||
|
||||
const file = self.base.file.?;
|
||||
const bytes = std.mem.sliceAsBytes(binary.items);
|
||||
var iovc_buffers: [buffers.len]std.os.iovec_const = undefined;
|
||||
for (iovc_buffers) |*iovc, i| {
|
||||
const bytes = std.mem.sliceAsBytes(buffers[i]);
|
||||
iovc.* = .{
|
||||
.iov_base = bytes.ptr,
|
||||
.iov_len = bytes.len
|
||||
};
|
||||
}
|
||||
|
||||
var file_size: u64 = 0;
|
||||
for (all_buffers) |iov| {
|
||||
for (iovc_buffers) |iov| {
|
||||
file_size += iov.iov_len;
|
||||
}
|
||||
|
||||
const file = self.base.file.?;
|
||||
try file.seekTo(0);
|
||||
try file.setEndPos(file_size);
|
||||
try file.pwritevAll(&all_buffers, 0);
|
||||
try file.pwritevAll(&iovc_buffers, 0);
|
||||
}
|
||||
|
||||
fn writeCapabilities(binary: *std.ArrayList(Word), target: std.Target) !void {
|
||||
@ -235,11 +241,3 @@ fn writeMemoryModel(binary: *std.ArrayList(Word), target: std.Target) !void {
|
||||
@enumToInt(addressing_model), @enumToInt(memory_model),
|
||||
});
|
||||
}
|
||||
|
||||
fn wordsToIovConst(words: []const Word) std.os.iovec_const {
|
||||
const bytes = std.mem.sliceAsBytes(words);
|
||||
return .{
|
||||
.iov_base = bytes.ptr,
|
||||
.iov_len = bytes.len,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user