Merge pull request #5978 from ziglang/stage2-dwarf-incr

self-hosted: line number debug information
master
Andrew Kelley 2020-08-04 20:53:47 +00:00 committed by GitHub
commit 952a397b0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 829 additions and 213 deletions

View File

@ -77,6 +77,9 @@ pub fn build(b: *Builder) !void {
const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false;
if (link_libc) exe.linkLibC();
const log_scopes = b.option([]const []const u8, "log", "Which log scopes to enable") orelse &[0][]const u8{};
exe.addBuildOption([]const []const u8, "log_scopes", log_scopes);
exe.addBuildOption(bool, "enable_tracy", tracy != null);
if (tracy) |tracy_path| {
const client_cpp = fs.path.join(

View File

@ -430,9 +430,9 @@ pub const Builder = struct {
const entry = self.user_input_options.getEntry(name) orelse return null;
entry.value.used = true;
switch (type_id) {
TypeId.Bool => switch (entry.value.value) {
UserValue.Flag => return true,
UserValue.Scalar => |s| {
.Bool => switch (entry.value.value) {
.Flag => return true,
.Scalar => |s| {
if (mem.eql(u8, s, "true")) {
return true;
} else if (mem.eql(u8, s, "false")) {
@ -443,21 +443,21 @@ pub const Builder = struct {
return null;
}
},
UserValue.List => {
.List => {
warn("Expected -D{} to be a boolean, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
},
TypeId.Int => panic("TODO integer options to build script", .{}),
TypeId.Float => panic("TODO float options to build script", .{}),
TypeId.Enum => switch (entry.value.value) {
UserValue.Flag => {
.Int => panic("TODO integer options to build script", .{}),
.Float => panic("TODO float options to build script", .{}),
.Enum => switch (entry.value.value) {
.Flag => {
warn("Expected -D{} to be a string, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.Scalar => |s| {
.Scalar => |s| {
if (std.meta.stringToEnum(T, s)) |enum_lit| {
return enum_lit;
} else {
@ -466,33 +466,35 @@ pub const Builder = struct {
return null;
}
},
UserValue.List => {
.List => {
warn("Expected -D{} to be a string, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
},
TypeId.String => switch (entry.value.value) {
UserValue.Flag => {
.String => switch (entry.value.value) {
.Flag => {
warn("Expected -D{} to be a string, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.List => {
.List => {
warn("Expected -D{} to be a string, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.Scalar => |s| return s,
.Scalar => |s| return s,
},
TypeId.List => switch (entry.value.value) {
UserValue.Flag => {
.List => switch (entry.value.value) {
.Flag => {
warn("Expected -D{} to be a list, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.Scalar => |s| return &[_][]const u8{s},
UserValue.List => |lst| return lst.span(),
.Scalar => |s| {
return self.allocator.dupe([]const u8, &[_][]const u8{s}) catch unreachable;
},
.List => |lst| return lst.span(),
},
}
}
@ -1706,9 +1708,19 @@ pub const LibExeObjStep = struct {
pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void {
const out = self.build_options_contents.outStream();
if (T == []const []const u8) {
out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable;
for (value) |slice| {
out.writeAll(" ") catch unreachable;
std.zig.renderStringLiteral(slice, out) catch unreachable;
out.writeAll(",\n") catch unreachable;
}
out.writeAll("};\n") catch unreachable;
return;
}
switch (@typeInfo(T)) {
.Enum => |enum_info| {
out.print("const {} = enum {{\n", .{@typeName(T)}) catch unreachable;
out.print("pub const {} = enum {{\n", .{@typeName(T)}) catch unreachable;
inline for (enum_info.fields) |field| {
out.print(" {},\n", .{field.name}) catch unreachable;
}

View File

@ -196,6 +196,10 @@ pub fn HashMap(
return self.unmanaged.getEntry(key);
}
pub fn getIndex(self: Self, key: K) ?usize {
return self.unmanaged.getIndex(key);
}
pub fn get(self: Self, key: K) ?V {
return self.unmanaged.get(key);
}
@ -479,17 +483,21 @@ pub fn HashMapUnmanaged(
}
pub fn getEntry(self: Self, key: K) ?*Entry {
const index = self.getIndex(key) orelse return null;
return &self.entries.items[index];
}
pub fn getIndex(self: Self, key: K) ?usize {
const header = self.index_header orelse {
// Linear scan.
const h = if (store_hash) hash(key) else {};
for (self.entries.items) |*item| {
for (self.entries.items) |*item, i| {
if (item.hash == h and eql(key, item.key)) {
return item;
return i;
}
}
return null;
};
switch (header.capacityIndexType()) {
.u8 => return self.getInternal(key, header, u8),
.u16 => return self.getInternal(key, header, u16),
@ -711,7 +719,7 @@ pub fn HashMapUnmanaged(
unreachable;
}
fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {
fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize {
const indexes = header.indexes(I);
const h = hash(key);
const start_index = header.constrainIndex(h);
@ -725,7 +733,7 @@ pub fn HashMapUnmanaged(
const entry = &self.entries.items[index.entry_index];
const hash_match = if (store_hash) h == entry.hash else true;
if (hash_match and eql(key, entry.key))
return entry;
return index.entry_index;
}
return null;
}

View File

@ -43,6 +43,22 @@ pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usi
return .{ .line = line, .column = column };
}
pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
var line: isize = 0;
if (end >= start) {
for (source[start..end]) |byte| switch (byte) {
'\n' => line += 1,
else => continue,
};
} else {
for (source[end..start]) |byte| switch (byte) {
'\n' => line -= 1,
else => continue,
};
}
return line;
}
/// Returns the standard file system basename of a binary generated by the Zig compiler.
pub fn binNameAlloc(
allocator: *std.mem.Allocator,

View File

@ -1299,6 +1299,10 @@ pub const Node = struct {
});
}
pub fn body(self: *const FnProto) ?*Node {
return self.getTrailer("body_node");
}
pub fn getTrailer(self: *const FnProto, comptime name: []const u8) ?TrailerFlags.Field(name) {
const trailers_start = @alignCast(
@alignOf(ParamDecl),
@ -1381,7 +1385,7 @@ pub const Node = struct {
.Invalid => {},
}
if (self.getTrailer("body_node")) |body_node| {
if (self.body()) |body_node| {
if (i < 1) return body_node;
i -= 1;
}
@ -1397,7 +1401,7 @@ pub const Node = struct {
}
pub fn lastToken(self: *const FnProto) TokenIndex {
if (self.getTrailer("body_node")) |body_node| return body_node.lastToken();
if (self.body()) |body_node| return body_node.lastToken();
switch (self.return_type) {
.Explicit, .InferErrorSet => |node| return node.lastToken(),
.Invalid => |tok| return tok,

View File

@ -6,6 +6,7 @@ const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const TypedValue = @import("TypedValue.zig");
const assert = std.debug.assert;
const log = std.log;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
@ -88,6 +89,9 @@ const WorkItem = union(enum) {
/// It may have already be analyzed, or it may have been determined
/// to be outdated; in this case perform semantic analysis again.
analyze_decl: *Decl,
/// The source file containing the Decl has been updated, and so the
/// Decl may need its line number information updated in the debug info.
update_line_number: *Decl,
};
pub const Export = struct {
@ -175,6 +179,13 @@ pub const Decl = struct {
/// This is populated regardless of semantic analysis and code generation.
link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty,
/// Represents the function in the linked output file, if the `Decl` is a function.
/// This is stored here and not in `Fn` because `Decl` survives across updates but
/// `Fn` does not.
/// TODO Look into making `Fn` a longer lived structure and moving this field there
/// to save on memory usage.
fn_link: link.File.Elf.SrcFn = link.File.Elf.SrcFn.empty,
contents_hash: std.zig.SrcHash,
/// The shallow set of other decls whose typed_value could possibly change if this Decl's
@ -235,7 +246,7 @@ pub const Decl = struct {
pub fn dump(self: *Decl) void {
const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src);
std.debug.warn("{}:{}:{} name={} status={}", .{
std.debug.print("{}:{}:{} name={} status={}", .{
self.scope.sub_file_path,
loc.line + 1,
loc.column + 1,
@ -243,9 +254,9 @@ pub const Decl = struct {
@tagName(self.analysis),
});
if (self.typedValueManaged()) |tvm| {
std.debug.warn(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val });
std.debug.print(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val });
}
std.debug.warn("\n", .{});
std.debug.print("\n", .{});
}
pub fn typedValueManaged(self: *Decl) ?*TypedValue.Managed {
@ -541,7 +552,7 @@ pub const Scope = struct {
pub fn dumpSrc(self: *File, src: usize) void {
const loc = std.zig.findLineColumn(self.source.bytes, src);
std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
}
pub fn getSource(self: *File, module: *Module) ![:0]const u8 {
@ -643,7 +654,7 @@ pub const Scope = struct {
pub fn dumpSrc(self: *ZIRModule, src: usize) void {
const loc = std.zig.findLineColumn(self.source.bytes, src);
std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
}
pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 {
@ -792,7 +803,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();
const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{
.root_name = root_name,
.root_src_dir_path = options.root_pkg.root_src_dir_path,
.root_pkg = options.root_pkg,
.target = options.target,
.output_mode = options.output_mode,
.link_mode = options.link_mode orelse .Static,
@ -885,6 +896,7 @@ pub fn deinit(self: *Module) void {
fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
for (export_list) |exp| {
gpa.free(exp.options.name);
gpa.destroy(exp);
}
gpa.free(export_list);
@ -943,7 +955,6 @@ pub fn update(self: *Module) !void {
}
self.link_error_flags = self.bin_file.errorFlags();
std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags});
// If there are any errors, we anticipate the source files being loaded
// to report error messages. Otherwise we unload all source files to save memory.
@ -1057,22 +1068,14 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
error.AnalysisFail => {
decl.analysis = .dependency_failure;
},
error.CGenFailure => {
// Error is handled by CBE, don't try adding it again
},
else => {
try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1);
const result = self.failed_decls.getOrPutAssumeCapacity(decl);
if (result.found_existing) {
std.debug.panic("Internal error: attempted to override error '{}' with 'unable to codegen: {}'", .{ result.entry.value.msg, @errorName(err) });
} else {
result.entry.value = try ErrorMsg.create(
self.gpa,
decl.src(),
"unable to codegen: {}",
.{@errorName(err)},
);
}
self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
self.gpa,
decl.src(),
"unable to codegen: {}",
.{@errorName(err)},
));
decl.analysis = .codegen_failure_retryable;
},
};
@ -1084,6 +1087,18 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
error.AnalysisFail => continue,
};
},
.update_line_number => |decl| {
self.bin_file.updateDeclLineNumber(self, decl) catch |err| {
try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1);
self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
self.gpa,
decl.src(),
"unable to update line number: {}",
.{@errorName(err)},
));
decl.analysis = .codegen_failure_retryable;
};
},
};
}
@ -1101,12 +1116,10 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void {
.codegen_failure_retryable,
=> return error.AnalysisFail,
.complete, .outdated => blk: {
if (decl.generation == self.generation) {
assert(decl.analysis == .complete);
return;
}
//std.debug.warn("re-analyzing {}\n", .{decl.name});
.complete => return,
.outdated => blk: {
log.debug(.module, "re-analyzing {}\n", .{decl.name});
// The exports this Decl performs will be re-discovered, so we remove them here
// prior to re-analysis.
@ -1481,6 +1494,9 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
}
fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
const tracy = trace(@src());
defer tracy.end();
// We may be analyzing it for the first time, or this may be
// an incremental update. This code handles both cases.
const tree = try self.getAstTree(root_scope);
@ -1522,6 +1538,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
if (!srcHashEql(decl.contents_hash, contents_hash)) {
try self.markOutdatedDecl(decl);
decl.contents_hash = contents_hash;
} else if (decl.fn_link.len != 0) {
// TODO Look into detecting when this would be unnecessary by storing enough state
// in `Decl` to notice that the line number did not change.
self.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl });
}
}
} else {
@ -1540,7 +1560,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
// Handle explicitly deleted decls from the source code. Not to be confused
// with when we delete decls because they are no longer referenced.
for (deleted_decls.items()) |entry| {
//std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name});
log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name});
try self.deleteDecl(entry.key);
}
}
@ -1569,7 +1589,6 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void {
const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name);
if (self.decl_table.get(name_hash)) |decl| {
deleted_decls.removeAssertDiscard(decl);
//std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents });
if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) {
try self.markOutdatedDecl(decl);
decl.contents_hash = src_decl.contents_hash;
@ -1594,7 +1613,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void {
// Handle explicitly deleted decls from the source code. Not to be confused
// with when we delete decls because they are no longer referenced.
for (deleted_decls.items()) |entry| {
//std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name});
log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name});
try self.deleteDecl(entry.key);
}
}
@ -1606,7 +1625,7 @@ fn deleteDecl(self: *Module, decl: *Decl) !void {
// not be present in the set, and this does nothing.
decl.scope.removeDecl(decl);
//std.debug.warn("deleting decl '{}'\n", .{decl.name});
log.debug(.module, "deleting decl '{}'\n", .{decl.name});
const name_hash = decl.fullyQualifiedNameHash();
self.decl_table.removeAssertDiscard(name_hash);
// Remove itself from its dependencies, because we are about to destroy the decl pointer.
@ -1668,6 +1687,7 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {
entry.value.destroy(self.gpa);
}
_ = self.symbol_exports.remove(exp.options.name);
self.gpa.free(exp.options.name);
self.gpa.destroy(exp);
}
self.gpa.free(kv.value);
@ -1692,17 +1712,17 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
const fn_zir = func.analysis.queued;
defer fn_zir.arena.promote(self.gpa).deinit();
func.analysis = .{ .in_progress = {} };
//std.debug.warn("set {} to in_progress\n", .{decl.name});
log.debug(.module, "set {} to in_progress\n", .{decl.name});
try zir_sema.analyzeBody(self, &inner_block.base, fn_zir.body);
const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items);
func.analysis = .{ .success = .{ .instructions = instructions } };
//std.debug.warn("set {} to success\n", .{decl.name});
log.debug(.module, "set {} to success\n", .{decl.name});
}
fn markOutdatedDecl(self: *Module, decl: *Decl) !void {
//std.debug.warn("mark {} outdated\n", .{decl.name});
log.debug(.module, "mark {} outdated\n", .{decl.name});
try self.work_queue.writeItem(.{ .analyze_decl = decl });
if (self.failed_decls.remove(decl)) |entry| {
entry.value.destroy(self.gpa);
@ -1768,7 +1788,7 @@ pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value {
return null;
}
pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const u8, exported_decl: *Decl) !void {
pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, borrowed_symbol_name: []const u8, exported_decl: *Decl) !void {
try self.ensureDeclAnalyzed(exported_decl);
const typed_value = exported_decl.typed_value.most_recent.typed_value;
switch (typed_value.ty.zigTypeTag()) {
@ -1782,6 +1802,9 @@ pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []co
const new_export = try self.gpa.create(Export);
errdefer self.gpa.destroy(new_export);
const symbol_name = try self.gpa.dupe(u8, borrowed_symbol_name);
errdefer self.gpa.free(symbol_name);
const owner_decl = scope.decl().?;
new_export.* = .{
@ -1794,7 +1817,7 @@ pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []co
};
// Add to export_owners table.
const eo_gop = self.export_owners.getOrPut(self.gpa, owner_decl) catch unreachable;
const eo_gop = self.export_owners.getOrPutAssumeCapacity(owner_decl);
if (!eo_gop.found_existing) {
eo_gop.entry.value = &[0]*Export{};
}
@ -1803,7 +1826,7 @@ pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []co
errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1);
// Add to exported_decl table.
const de_gop = self.decl_exports.getOrPut(self.gpa, exported_decl) catch unreachable;
const de_gop = self.decl_exports.getOrPutAssumeCapacity(exported_decl);
if (!de_gop.found_existing) {
de_gop.entry.value = &[0]*Export{};
}
@ -2811,7 +2834,7 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source");
const loc = std.zig.findLineColumn(source, inst.src);
if (inst.tag == .constant) {
std.debug.warn("constant ty={} val={} src={}:{}:{}\n", .{
std.debug.print("constant ty={} val={} src={}:{}:{}\n", .{
inst.ty,
inst.castTag(.constant).?.val,
zir_module.subFilePath(),
@ -2819,7 +2842,7 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
loc.column + 1,
});
} else if (inst.deaths == 0) {
std.debug.warn("{} ty={} src={}:{}:{}\n", .{
std.debug.print("{} ty={} src={}:{}:{}\n", .{
@tagName(inst.tag),
inst.ty,
zir_module.subFilePath(),
@ -2827,7 +2850,7 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
loc.column + 1,
});
} else {
std.debug.warn("{} ty={} deaths={b} src={}:{}:{}\n", .{
std.debug.print("{} ty={} deaths={b} src={}:{}:{}\n", .{
@tagName(inst.tag),
inst.ty,
inst.deaths,

View File

@ -120,6 +120,8 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
var scope = parent_scope;
for (block_node.statements()) |statement| {
const src = scope.tree().token_locs[statement.firstToken()].start;
_ = try addZIRNoOp(mod, scope, src, .dbg_stmt);
switch (statement.tag) {
.VarDecl => {
const var_decl_node = statement.castTag(.VarDecl).?;
@ -146,7 +148,6 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
else => {
const possibly_unused_result = try expr(mod, scope, .none, statement);
if (!possibly_unused_result.tag.isNoReturn()) {
const src = scope.tree().token_locs[statement.firstToken()].start;
_ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result);
}
},

View File

@ -12,6 +12,11 @@ const ErrorMsg = Module.ErrorMsg;
const Target = std.Target;
const Allocator = mem.Allocator;
const trace = @import("tracy.zig").trace;
const DW = std.dwarf;
const leb128 = std.debug.leb;
// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.
// zig fmt: off
/// The codegen-related data that is stored in `ir.Inst.Block` instructions.
pub const BlockData = struct {
@ -44,6 +49,7 @@ pub fn generateSymbol(
src: usize,
typed_value: TypedValue,
code: *std.ArrayList(u8),
dbg_line: *std.ArrayList(u8),
) GenerateSymbolError!Result {
const tracy = trace(@src());
defer tracy.end();
@ -51,57 +57,57 @@ pub fn generateSymbol(
switch (typed_value.ty.zigTypeTag()) {
.Fn => {
switch (bin_file.base.options.target.cpu.arch) {
//.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code),
//.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code),
//.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code),
//.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code),
//.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code),
//.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code),
//.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code),
//.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code),
//.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code),
//.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code),
//.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code),
//.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code),
//.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code),
//.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code),
//.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code),
//.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code),
//.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code),
//.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code),
//.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code),
//.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code),
//.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code),
//.riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code),
//.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code),
//.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code),
//.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code),
//.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code),
//.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code),
//.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code),
//.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code),
//.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code),
//.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code),
.x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code),
//.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code),
//.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code),
//.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code),
//.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code),
//.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code),
//.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code),
//.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code),
//.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code),
//.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code),
//.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code),
//.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code),
//.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code),
//.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code),
//.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code),
//.wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code),
//.wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code),
//.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code),
//.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code),
//.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code),
//.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, dbg_line),
.x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
//.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, dbg_line),
else => @panic("Backend architectures that don't have good support yet are commented out, to improve compilation performance. If you are interested in one of these other backends feel free to uncomment them. Eventually these will be completed, but stage1 is slow and a memory hog."),
}
},
@ -114,7 +120,7 @@ pub fn generateSymbol(
switch (try generateSymbol(bin_file, src, .{
.ty = typed_value.ty.elemType(),
.val = sentinel,
}, code)) {
}, code, dbg_line)) {
.appended => return Result{ .appended = {} },
.externally_managed => |slice| {
code.appendSliceAssumeCapacity(slice);
@ -206,6 +212,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
target: *const std.Target,
mod_fn: *const Module.Fn,
code: *std.ArrayList(u8),
dbg_line: *std.ArrayList(u8),
err_msg: ?*ErrorMsg,
args: []MCValue,
ret_mcv: MCValue,
@ -214,6 +221,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
src: usize,
stack_align: u32,
/// Byte offset within the source file.
prev_di_src: usize,
/// Relative to the beginning of `code`.
prev_di_pc: usize,
/// Used to find newlines and count line deltas.
source: []const u8,
/// Byte offset within the source file of the ending curly.
rbrace_src: usize,
/// The value is an offset into the `Function` `code` from the beginning.
/// To perform the reloc, write 32-bit signed little-endian integer
/// which is a relative jump, based on the address following the reloc.
@ -365,6 +381,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
src: usize,
typed_value: TypedValue,
code: *std.ArrayList(u8),
dbg_line: *std.ArrayList(u8),
) GenerateSymbolError!Result {
const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
@ -379,12 +396,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const branch = try branch_stack.addOne();
branch.* = .{};
const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: {
if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| {
const tree = scope_file.contents.tree;
const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
const block = fn_proto.body().?.castTag(.Block).?;
const lbrace_src = tree.token_locs[block.lbrace].start;
const rbrace_src = tree.token_locs[block.rbrace].start;
break :blk .{ .lbrace_src = lbrace_src, .rbrace_src = rbrace_src, .source = tree.source };
} else if (module_fn.owner_decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
const byte_off = zir_module.contents.module.decls[module_fn.owner_decl.src_index].inst.src;
break :blk .{ .lbrace_src = byte_off, .rbrace_src = byte_off, .source = zir_module.source.bytes };
} else {
unreachable;
}
};
var function = Self{
.gpa = bin_file.allocator,
.target = &bin_file.base.options.target,
.bin_file = bin_file,
.mod_fn = module_fn,
.code = code,
.dbg_line = dbg_line,
.err_msg = null,
.args = undefined, // populated after `resolveCallingConventionValues`
.ret_mcv = undefined, // populated after `resolveCallingConventionValues`
@ -393,6 +427,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.branch_stack = &branch_stack,
.src = src,
.stack_align = undefined,
.prev_di_pc = 0,
.prev_di_src = src_data.lbrace_src,
.rbrace_src = src_data.rbrace_src,
.source = src_data.source,
};
defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
@ -431,20 +469,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// TODO During semantic analysis, check if there are no function calls. If there
// are none, here we can omit the part where we subtract and then add rsp.
self.code.appendSliceAssumeCapacity(&[_]u8{
// push rbp
0x55,
// mov rbp, rsp
0x48,
0x89,
0xe5,
// sub rsp, imm32 (with reloc)
0x48,
0x81,
0xec,
0x55, // push rbp
0x48, 0x89, 0xe5, // mov rbp, rsp
0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc)
});
const reloc_index = self.code.items.len;
self.code.items.len += 4;
try self.dbgSetPrologueEnd();
try self.genBody(self.mod_fn.analysis.success);
const stack_end = self.branch_stack.items[0].max_end_stack;
@ -467,6 +499,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
mem.writeIntLittle(i32, self.code.items[jmp_reloc..][0..4], s32_amt);
}
// Important to be after the possible self.code.items.len -= 5 above.
try self.dbgSetEpilogueBegin();
try self.code.ensureCapacity(self.code.items.len + 9);
// add rsp, x
if (aligned_stack_end > math.maxInt(i8)) {
@ -485,13 +520,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
0xc3, // ret
});
} else {
try self.dbgSetPrologueEnd();
try self.genBody(self.mod_fn.analysis.success);
try self.dbgSetEpilogueBegin();
}
},
else => {
try self.dbgSetPrologueEnd();
try self.genBody(self.mod_fn.analysis.success);
try self.dbgSetEpilogueBegin();
},
}
// Drop them off at the rbrace.
try self.dbgAdvancePCAndLine(self.rbrace_src);
}
fn genBody(self: *Self, body: ir.Body) InnerError!void {
@ -508,6 +549,38 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
fn dbgSetPrologueEnd(self: *Self) InnerError!void {
try self.dbg_line.append(DW.LNS_set_prologue_end);
try self.dbgAdvancePCAndLine(self.prev_di_src);
}
fn dbgSetEpilogueBegin(self: *Self) InnerError!void {
try self.dbg_line.append(DW.LNS_set_epilogue_begin);
try self.dbgAdvancePCAndLine(self.prev_di_src);
}
fn dbgAdvancePCAndLine(self: *Self, src: usize) InnerError!void {
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table, and changing ir.Inst from storing byte offset to token. Currently
// this involves scanning over the source code for newlines
// (but only from the previous byte offset to the new one).
const delta_line = std.zig.lineDelta(self.source, self.prev_di_src, src);
const delta_pc = self.code.items.len - self.prev_di_pc;
self.prev_di_src = src;
self.prev_di_pc = self.code.items.len;
// TODO Look into using the DWARF special opcodes to compress this data. It lets you emit
// single-byte opcodes that add different numbers to both the PC and the line number
// at the same time.
try self.dbg_line.ensureCapacity(self.dbg_line.items.len + 11);
self.dbg_line.appendAssumeCapacity(DW.LNS_advance_pc);
leb128.writeULEB128(self.dbg_line.writer(), delta_pc) catch unreachable;
if (delta_line != 0) {
self.dbg_line.appendAssumeCapacity(DW.LNS_advance_line);
leb128.writeILEB128(self.dbg_line.writer(), delta_line) catch unreachable;
}
self.dbg_line.appendAssumeCapacity(DW.LNS_copy);
}
fn processDeath(self: *Self, inst: *ir.Inst) void {
const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
const entry = branch.inst_table.getEntry(inst) orelse return;
@ -543,6 +616,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq),
.condbr => return self.genCondBr(inst.castTag(.condbr).?),
.constant => unreachable, // excluded from function bodies
.dbg_stmt => return self.genDbgStmt(inst.castTag(.dbg_stmt).?),
.floatcast => return self.genFloatCast(inst.castTag(.floatcast).?),
.intcast => return self.genIntCast(inst.castTag(.intcast).?),
.isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?),
@ -1106,6 +1180,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
try self.dbgAdvancePCAndLine(inst.base.src);
return MCValue.none;
}
fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue {
switch (arch) {
.x86_64 => {

View File

@ -89,17 +89,17 @@ fn genFn(file: *C, decl: *Decl) !void {
const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
const instructions = func.analysis.success.instructions;
if (instructions.len > 0) {
try writer.writeAll("\n");
for (instructions) |inst| {
try writer.writeAll("\n ");
switch (inst.tag) {
.assembly => try genAsm(file, inst.castTag(.assembly).?, decl),
.call => try genCall(file, inst.castTag(.call).?, decl),
.ret => try genRet(file, inst.castTag(.ret).?, decl, tv.ty.fnReturnType()),
.retvoid => try file.main.writer().print("return;", .{}),
.retvoid => try file.main.writer().print(" return;\n", .{}),
.dbg_stmt => try genDbgStmt(file, inst.castTag(.dbg_stmt).?, decl),
else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
}
}
try writer.writeAll("\n");
}
try writer.writeAll("}\n\n");
@ -112,6 +112,7 @@ fn genRet(file: *C, inst: *Inst.UnOp, decl: *Decl, expected_return_type: Type) !
fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
const writer = file.main.writer();
const header = file.header.writer();
try writer.writeAll(" ");
if (inst.func.castTag(.constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const target = func_val.func.owner_decl;
@ -126,7 +127,7 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
try renderFunctionSignature(file, header, target);
try header.writeAll(";\n");
}
try writer.print("{}();", .{tname});
try writer.print("{}();\n", .{tname});
} else {
return file.fail(decl.src(), "TODO non-function call target?", .{});
}
@ -138,8 +139,13 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
}
}
fn genDbgStmt(file: *C, inst: *Inst.NoOp, decl: *Decl) !void {
// TODO emit #line directive here with line number and filename
}
fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void {
const writer = file.main.writer();
try writer.writeAll(" ");
for (as.inputs) |i, index| {
if (i[0] == '{' and i[i.len - 1] == '}') {
const reg = i[1 .. i.len - 1];
@ -187,5 +193,5 @@ fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void {
}
}
}
try writer.writeAll(");");
try writer.writeAll(");\n");
}

View File

@ -65,6 +65,7 @@ pub const Inst = struct {
cmp_neq,
condbr,
constant,
dbg_stmt,
isnonnull,
isnull,
/// Read a value from a pointer.
@ -88,6 +89,7 @@ pub const Inst = struct {
.unreach,
.arg,
.breakpoint,
.dbg_stmt,
=> NoOp,
.ref,

View File

@ -11,6 +11,9 @@ const c_codegen = @import("codegen/c.zig");
const log = std.log;
const DW = std.dwarf;
const trace = @import("tracy.zig").trace;
const leb128 = std.debug.leb;
const Package = @import("Package.zig");
const Value = @import("value.zig").Value;
// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.
// zig fmt: off
@ -24,7 +27,7 @@ pub const Options = struct {
object_format: std.builtin.ObjectFormat,
optimize_mode: std.builtin.Mode,
root_name: []const u8,
root_src_dir_path: []const u8,
root_pkg: *const Package,
/// Used for calculating how much space to reserve for symbols in case the binary file
/// does not already have a symbol table.
symbol_count_hint: u64 = 32,
@ -82,6 +85,13 @@ pub const File = struct {
}
}
pub fn updateDeclLineNumber(base: *File, module: *Module, decl: *Module.Decl) !void {
switch (base.tag) {
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
.c => {},
}
}
pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
switch (base.tag) {
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
@ -200,7 +210,7 @@ pub const File = struct {
pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) !void {
self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args);
return error.CGenFailure;
return error.AnalysisFail;
}
pub fn deinit(self: *File.C) void {
@ -214,7 +224,7 @@ pub const File = struct {
pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void {
c_codegen.generate(self, decl) catch |err| {
if (err == error.CGenFailure) {
if (err == error.AnalysisFail) {
try module.failed_decls.put(module.gpa, decl, self.error_msg);
}
return err;
@ -291,6 +301,7 @@ pub const File = struct {
debug_abbrev_section_index: ?u16 = null,
debug_str_section_index: ?u16 = null,
debug_aranges_section_index: ?u16 = null,
debug_line_section_index: ?u16 = null,
debug_abbrev_table_offset: ?u64 = null,
@ -318,6 +329,7 @@ pub const File = struct {
debug_info_section_dirty: bool = false,
debug_abbrev_section_dirty: bool = false,
debug_aranges_section_dirty: bool = false,
debug_line_header_dirty: bool = false,
error_flags: ErrorFlags = ErrorFlags{},
@ -339,6 +351,12 @@ pub const File = struct {
text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){},
last_text_block: ?*TextBlock = null,
/// A list of `SrcFn` whose Line Number Programs have surplus capacity.
/// This is the same concept as `text_block_free_list`; see those doc comments.
dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{},
dbg_line_fn_first: ?*SrcFn = null,
dbg_line_fn_last: ?*SrcFn = null,
/// `alloc_num / alloc_den` is the factor of padding when allocating.
const alloc_num = 4;
const alloc_den = 3;
@ -402,6 +420,26 @@ pub const File = struct {
sym_index: ?u32 = null,
};
pub const SrcFn = struct {
/// Offset from the beginning of the Debug Line Program header that contains this function.
off: u32,
/// Size of the line number program component belonging to this function, not
/// including padding.
len: u32,
/// Points to the previous and next neighbors, based on the offset from .debug_line.
/// This can be used to find, for example, the capacity of this `SrcFn`.
prev: ?*SrcFn,
next: ?*SrcFn,
pub const empty: SrcFn = .{
.off = 0,
.len = 0,
.prev = null,
.next = null,
};
};
pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
assert(options.object_format == .elf);
@ -514,6 +552,7 @@ pub const File = struct {
self.local_symbol_free_list.deinit(self.allocator);
self.offset_table_free_list.deinit(self.allocator);
self.text_block_free_list.deinit(self.allocator);
self.dbg_line_fn_free_list.deinit(self.allocator);
self.offset_table.deinit(self.allocator);
if (self.owns_file_handle) {
if (self.file) |f| f.close();
@ -538,6 +577,14 @@ pub const File = struct {
});
}
fn getDebugLineProgramOff(self: Elf) u32 {
return self.dbg_line_fn_first.?.off;
}
fn getDebugLineProgramEnd(self: Elf) u32 {
return self.dbg_line_fn_last.?.off + self.dbg_line_fn_last.?.len;
}
/// Returns end pos of collision, if any.
fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32;
@ -585,6 +632,8 @@ pub const File = struct {
}
fn allocatedSize(self: *Elf, start: u64) u64 {
if (start == 0)
return 0;
var min_pos: u64 = std.math.maxInt(u64);
if (self.shdr_table_offset) |off| {
if (off > start and off < min_pos) min_pos = off;
@ -611,6 +660,7 @@ pub const File = struct {
return start;
}
/// TODO Improve this to use a table.
fn makeString(self: *Elf, bytes: []const u8) !u32 {
try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1);
const result = self.shstrtab.items.len;
@ -619,6 +669,7 @@ pub const File = struct {
return @intCast(u32, result);
}
/// TODO Improve this to use a table.
fn makeDebugString(self: *Elf, bytes: []const u8) !u32 {
try self.debug_strtab.ensureCapacity(self.allocator, self.debug_strtab.items.len + bytes.len + 1);
const result = self.debug_strtab.items.len;
@ -645,10 +696,7 @@ pub const File = struct {
.p32 => true,
.p64 => false,
};
const ptr_size: u8 = switch (self.ptr_width) {
.p32 => 4,
.p64 => 8,
};
const ptr_size: u8 = self.ptrWidthBytes();
if (self.phdr_load_re_index == null) {
self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len);
const file_size = self.base.options.program_code_size_hint;
@ -713,27 +761,6 @@ pub const File = struct {
self.shstrtab_dirty = true;
self.shdr_table_dirty = true;
}
if (self.debug_str_section_index == null) {
self.debug_str_section_index = @intCast(u16, self.sections.items.len);
assert(self.debug_strtab.items.len == 0);
try self.debug_strtab.append(self.allocator, 0); // need a 0 at position 0
const off = self.findFreeSpace(self.debug_strtab.items.len, 1);
log.debug(.link, "found debug_strtab free space 0x{x} to 0x{x}\n", .{ off, off + self.debug_strtab.items.len });
try self.sections.append(self.allocator, .{
.sh_name = try self.makeString(".debug_str"),
.sh_type = elf.SHT_PROGBITS,
.sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS,
.sh_addr = 0,
.sh_offset = off,
.sh_size = self.debug_strtab.items.len,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 1,
.sh_entsize = 1,
});
self.debug_strtab_dirty = true;
self.shdr_table_dirty = true;
}
if (self.text_section_index == null) {
self.text_section_index = @intCast(u16, self.sections.items.len);
const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
@ -794,6 +821,24 @@ pub const File = struct {
self.shdr_table_dirty = true;
try self.writeSymbol(0);
}
if (self.debug_str_section_index == null) {
self.debug_str_section_index = @intCast(u16, self.sections.items.len);
assert(self.debug_strtab.items.len == 0);
try self.sections.append(self.allocator, .{
.sh_name = try self.makeString(".debug_str"),
.sh_type = elf.SHT_PROGBITS,
.sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS,
.sh_addr = 0,
.sh_offset = 0,
.sh_size = self.debug_strtab.items.len,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = 1,
.sh_entsize = 1,
});
self.debug_strtab_dirty = true;
self.shdr_table_dirty = true;
}
if (self.debug_info_section_index == null) {
self.debug_info_section_index = @intCast(u16, self.sections.items.len);
@ -869,6 +914,31 @@ pub const File = struct {
self.shdr_table_dirty = true;
self.debug_aranges_section_dirty = true;
}
if (self.debug_line_section_index == null) {
self.debug_line_section_index = @intCast(u16, self.sections.items.len);
const file_size_hint = 250;
const p_align = 1;
const off = self.findFreeSpace(file_size_hint, p_align);
log.debug(.link, "found .debug_line free space 0x{x} to 0x{x}\n", .{
off,
off + file_size_hint,
});
try self.sections.append(self.allocator, .{
.sh_name = try self.makeString(".debug_line"),
.sh_type = elf.SHT_PROGBITS,
.sh_flags = 0,
.sh_addr = 0,
.sh_offset = off,
.sh_size = file_size_hint,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = p_align,
.sh_entsize = 0,
});
self.shdr_table_dirty = true;
self.debug_line_header_dirty = true;
}
const shsize: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Shdr),
.p64 => @sizeOf(elf.Elf64_Shdr),
@ -906,9 +976,10 @@ pub const File = struct {
pub fn flush(self: *Elf) !void {
const target_endian = self.base.options.target.cpu.arch.endian();
const foreign_endian = target_endian != std.Target.current.cpu.arch.endian();
const ptr_width_bytes: u8 = switch (self.ptr_width) {
const ptr_width_bytes: u8 = self.ptrWidthBytes();
const init_len_size: usize = switch (self.ptr_width) {
.p32 => 4,
.p64 => 8,
.p64 => 12,
};
// Unfortunately these have to be buffered and done at the end because ELF does not allow
@ -922,7 +993,7 @@ pub const File = struct {
// we can simply append these bytes.
const abbrev_buf = [_]u8{
1, DW.TAG_compile_unit, DW.CHILDREN_no, // header
//DW.AT_stmt_list, DW.FORM_data4, TODO
DW.AT_stmt_list, DW.FORM_sec_offset,
DW.AT_low_pc , DW.FORM_addr,
DW.AT_high_pc , DW.FORM_addr,
DW.AT_name , DW.FORM_strp,
@ -969,27 +1040,23 @@ pub const File = struct {
// not including the initial length itself.
// We have to come back and write it later after we know the size.
const init_len_index = di_buf.items.len;
switch (self.ptr_width) {
.p32 => di_buf.items.len += 4,
.p64 => di_buf.items.len += 12,
}
di_buf.items.len += init_len_size;
const after_init_len = di_buf.items.len;
mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 5, target_endian); // DWARF version
di_buf.appendAssumeCapacity(DW.UT_compile);
mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // DWARF version
const abbrev_offset = self.debug_abbrev_table_offset.?;
switch (self.ptr_width) {
.p32 => {
di_buf.appendAssumeCapacity(4); // address size
mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset), target_endian);
di_buf.appendAssumeCapacity(4); // address size
},
.p64 => {
di_buf.appendAssumeCapacity(8); // address size
mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), abbrev_offset, target_endian);
di_buf.appendAssumeCapacity(8); // address size
},
}
// Write the form for the compile unit, which must match the abbrev table above.
const name_strp = try self.makeDebugString(self.base.options.root_name);
const comp_dir_strp = try self.makeDebugString(self.base.options.root_src_dir_path);
const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path);
const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path);
const producer_strp = try self.makeDebugString("zig (TODO version here)");
// Currently only one compilation unit is supported, so the address range is simply
// identical to the main program header virtual address and memory size.
@ -998,7 +1065,7 @@ pub const File = struct {
const high_pc = text_phdr.p_vaddr + text_phdr.p_memsz;
di_buf.appendAssumeCapacity(1); // abbrev tag, matching the value from the abbrev table header
//DW.AT_stmt_list, DW.FORM_data4, TODO line information
self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // DW.AT_stmt_list, DW.FORM_sec_offset
self.writeDwarfAddrAssumeCapacity(&di_buf, low_pc);
self.writeDwarfAddrAssumeCapacity(&di_buf, high_pc);
self.writeDwarfAddrAssumeCapacity(&di_buf, name_strp);
@ -1056,10 +1123,7 @@ pub const File = struct {
// not including the initial length itself.
// We have to come back and write it later after we know the size.
const init_len_index = di_buf.items.len;
switch (self.ptr_width) {
.p32 => di_buf.items.len += 4,
.p64 => di_buf.items.len += 12,
}
di_buf.items.len += init_len_size;
const after_init_len = di_buf.items.len;
mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2, target_endian); // version
// When more than one compilation unit is supported, this will be the offset to it.
@ -1116,6 +1180,100 @@ pub const File = struct {
self.debug_aranges_section_dirty = false;
}
if (self.debug_line_header_dirty) {
const dbg_line_prg_off = self.getDebugLineProgramOff();
const dbg_line_prg_end = self.getDebugLineProgramEnd();
assert(dbg_line_prg_end != 0);
const debug_line_sect = &self.sections.items[self.debug_line_section_index.?];
var di_buf = std.ArrayList(u8).init(self.allocator);
defer di_buf.deinit();
// The size of this header is variable, depending on the number of directories,
// files, and padding. We have a function to compute the upper bound size, however,
// because it's needed for determining where to put the offset of the first `SrcFn`.
try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes());
// initial length - length of the .debug_line contribution for this compilation unit,
// not including the initial length itself.
const after_init_len = di_buf.items.len + init_len_size;
const init_len = dbg_line_prg_end - after_init_len;
switch (self.ptr_width) {
.p32 => {
mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian);
},
.p64 => {
di_buf.appendNTimesAssumeCapacity(0xff, 4);
mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian);
},
}
mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // version
// Empirically, debug info consumers do not respect this field, or otherwise
// consider it to be an error when it does not point exactly to the end of the header.
// Therefore we rely on the NOP jump at the beginning of the Line Number Program for
// padding rather than this field.
const before_header_len = di_buf.items.len;
di_buf.items.len += ptr_width_bytes; // We will come back and write this.
const after_header_len = di_buf.items.len;
const opcode_base = DW.LNS_set_isa + 1;
di_buf.appendSliceAssumeCapacity(&[_]u8{
1, // minimum_instruction_length
1, // maximum_operations_per_instruction
1, // default_is_stmt
1, // line_base (signed)
1, // line_range
opcode_base,
// Standard opcode lengths. The number of items here is based on `opcode_base`.
// The value is the number of LEB128 operands the instruction takes.
0, // `DW.LNS_copy`
1, // `DW.LNS_advance_pc`
1, // `DW.LNS_advance_line`
1, // `DW.LNS_set_file`
1, // `DW.LNS_set_column`
0, // `DW.LNS_negate_stmt`
0, // `DW.LNS_set_basic_block`
0, // `DW.LNS_const_add_pc`
1, // `DW.LNS_fixed_advance_pc`
0, // `DW.LNS_set_prologue_end`
0, // `DW.LNS_set_epilogue_begin`
1, // `DW.LNS_set_isa`
0, // include_directories (none except the compilation unit cwd)
});
// file_names[0]
di_buf.appendSliceAssumeCapacity(self.base.options.root_pkg.root_src_path); // relative path name
di_buf.appendSliceAssumeCapacity(&[_]u8{
0, // null byte for the relative path name
0, // directory_index
0, // mtime (TODO supply this)
0, // file size bytes (TODO supply this)
0, // file_names sentinel
});
const header_len = di_buf.items.len - after_header_len;
switch (self.ptr_width) {
.p32 => {
mem.writeInt(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len), target_endian);
},
.p64 => {
mem.writeInt(u64, di_buf.items[before_header_len..][0..8], header_len, target_endian);
},
}
// We use NOPs because consumers empirically do not respect the header length field.
if (di_buf.items.len > dbg_line_prg_off) {
// Move the first N files to the end to make more padding for the header.
@panic("TODO: handle .debug_line header exceeding its padding");
}
const jmp_amt = dbg_line_prg_off - di_buf.items.len;
try self.pwriteWithNops(0, di_buf.items, jmp_amt, debug_line_sect.sh_offset);
self.debug_line_header_dirty = false;
}
if (self.phdr_table_dirty) {
const phsize: u64 = switch (self.ptr_width) {
@ -1174,7 +1332,7 @@ pub const File = struct {
shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1);
}
shstrtab_sect.sh_size = needed_size;
log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size });
log.debug(.link, "writing shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size });
try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset);
if (!self.shdr_table_dirty) {
@ -1263,6 +1421,7 @@ pub const File = struct {
assert(!self.debug_info_section_dirty);
assert(!self.debug_abbrev_section_dirty);
assert(!self.debug_aranges_section_dirty);
assert(!self.debug_line_header_dirty);
assert(!self.phdr_table_dirty);
assert(!self.shdr_table_dirty);
assert(!self.shstrtab_dirty);
@ -1580,11 +1739,8 @@ pub const File = struct {
pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void {
if (decl.link.local_sym_index != 0) return;
// Here we also ensure capacity for the free lists so that they can be appended to without fail.
try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);
try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);
try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);
if (self.local_symbol_free_list.popOrNull()) |i| {
log.debug(.link, "reusing symbol index {} for {}\n", .{ i, decl.name });
@ -1617,15 +1773,37 @@ pub const File = struct {
}
pub fn freeDecl(self: *Elf, decl: *Module.Decl) void {
// Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
self.freeTextBlock(&decl.link);
if (decl.link.local_sym_index != 0) {
self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index);
self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index);
self.local_symbol_free_list.append(self.allocator, decl.link.local_sym_index) catch {};
self.offset_table_free_list.append(self.allocator, decl.link.offset_table_index) catch {};
self.local_symbols.items[decl.link.local_sym_index].st_info = 0;
decl.link.local_sym_index = 0;
}
// TODO make this logic match freeTextBlock. Maybe abstract the logic out since the same thing
// is desired for both.
_ = self.dbg_line_fn_free_list.remove(&decl.fn_link);
if (decl.fn_link.prev) |prev| {
_ = self.dbg_line_fn_free_list.put(self.allocator, prev, {}) catch {};
prev.next = decl.fn_link.next;
if (decl.fn_link.next) |next| {
next.prev = prev;
} else {
self.dbg_line_fn_last = prev;
}
} else if (decl.fn_link.next) |next| {
self.dbg_line_fn_first = next;
next.prev = null;
}
if (self.dbg_line_fn_first == &decl.fn_link) {
self.dbg_line_fn_first = null;
}
if (self.dbg_line_fn_last == &decl.fn_link) {
self.dbg_line_fn_last = null;
}
}
pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
@ -1635,8 +1813,67 @@ pub const File = struct {
var code_buffer = std.ArrayList(u8).init(self.allocator);
defer code_buffer.deinit();
var dbg_line_buffer = std.ArrayList(u8).init(self.allocator);
defer dbg_line_buffer.deinit();
const typed_value = decl.typed_value.most_recent.typed_value;
const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) {
const is_fn: bool = switch (typed_value.ty.zigTypeTag()) {
.Fn => true,
else => false,
};
if (is_fn) {
// For functions we need to add a prologue to the debug line program.
try dbg_line_buffer.ensureCapacity(26);
const line_off: u28 = blk: {
if (decl.scope.cast(Module.Scope.File)) |scope_file| {
const tree = scope_file.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
const block = fn_proto.body().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
break :blk @intCast(u28, line_delta);
} else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src;
const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off);
break :blk @intCast(u28, line_delta);
} else {
unreachable;
}
};
const ptr_width_bytes = self.ptrWidthBytes();
dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
DW.LNS_extended_op,
ptr_width_bytes + 1,
DW.LNE_set_address,
});
// This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`.
assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len);
dbg_line_buffer.items.len += ptr_width_bytes;
dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line);
// This is the "relocatable" relative line offset from the previous function's end curly
// to this function's begin curly.
assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len);
// Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later.
leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off);
dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file);
assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len);
// Once we support more than one source file, this will have the ability to be more
// than one possible value.
const file_index = 1;
leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index);
// Emit a line for the begin curly with prologue_end=false. The codegen will
// do the work of setting prologue_end=true and epilogue_begin=true.
dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy);
}
const res = try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer, &dbg_line_buffer);
const code = switch (res) {
.externally_managed => |x| x,
.appended => code_buffer.items,
.fail => |em| {
@ -1648,10 +1885,7 @@ pub const File = struct {
const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {
.Fn => elf.STT_FUNC,
else => elf.STT_OBJECT,
};
const stt_bits: u8 = if (is_fn) elf.STT_FUNC else elf.STT_OBJECT;
assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()
const local_sym = &self.local_symbols.items[decl.link.local_sym_index];
@ -1704,6 +1938,94 @@ pub const File = struct {
const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset;
try self.file.?.pwriteAll(code, file_offset);
// If the Decl is a function, we need to update the .debug_line program.
if (is_fn) {
// Perform the relocation based on vaddr.
const target_endian = self.base.options.target.cpu.arch.endian();
switch (self.ptr_width) {
.p32 => {
const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4];
mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian);
},
.p64 => {
const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8];
mem.writeInt(u64, ptr, local_sym.st_value, target_endian);
},
}
try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence });
// Now we have the full contents and may allocate a region to store it.
const debug_line_sect = &self.sections.items[self.debug_line_section_index.?];
const src_fn = &decl.fn_link;
src_fn.len = @intCast(u32, dbg_line_buffer.items.len);
if (self.dbg_line_fn_last) |last| {
if (src_fn.next) |next| {
// Update existing function - non-last item.
if (src_fn.off + src_fn.len + min_nop_size > next.off) {
// It grew too big, so we move it to a new location.
if (src_fn.prev) |prev| {
_ = self.dbg_line_fn_free_list.put(self.allocator, prev, {}) catch {};
prev.next = src_fn.next;
}
next.prev = src_fn.prev;
src_fn.next = null;
// Populate where it used to be with NOPs.
const file_pos = debug_line_sect.sh_offset + src_fn.off;
try self.pwriteWithNops(0, &[0]u8{}, src_fn.len, file_pos);
// TODO Look at the free list before appending at the end.
src_fn.prev = last;
last.next = src_fn;
self.dbg_line_fn_last = src_fn;
src_fn.off = last.off + (last.len * alloc_num / alloc_den);
}
} else if (src_fn.prev == null) {
// Append new function.
// TODO Look at the free list before appending at the end.
src_fn.prev = last;
last.next = src_fn;
self.dbg_line_fn_last = src_fn;
src_fn.off = last.off + (last.len * alloc_num / alloc_den);
}
} else {
// This is the first function of the Line Number Program.
self.dbg_line_fn_first = src_fn;
self.dbg_line_fn_last = src_fn;
src_fn.off = self.dbgLineNeededHeaderBytes() * alloc_num / alloc_den;
}
const last_src_fn = self.dbg_line_fn_last.?;
const needed_size = last_src_fn.off + last_src_fn.len;
if (needed_size != debug_line_sect.sh_size) {
if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) {
const new_offset = self.findFreeSpace(needed_size, 1);
const existing_size = last_src_fn.off;
log.debug(.link, "moving .debug_line section: {} bytes from 0x{x} to 0x{x}\n", .{
existing_size,
debug_line_sect.sh_offset,
new_offset,
});
const amt = try self.file.?.copyRangeAll(debug_line_sect.sh_offset, self.file.?, new_offset, existing_size);
if (amt != existing_size) return error.InputOutput;
debug_line_sect.sh_offset = new_offset;
}
debug_line_sect.sh_size = needed_size;
self.shdr_table_dirty = true; // TODO look into making only the one section dirty
self.debug_line_header_dirty = true;
}
const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0;
const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0;
// We only have support for one compilation unit so far, so the offsets are directly
// from the .debug_line section.
const file_pos = debug_line_sect.sh_offset + src_fn.off;
try self.pwriteWithNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos);
}
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
return self.updateDeclExports(module, decl, decl_exports);
@ -1719,10 +2041,7 @@ pub const File = struct {
const tracy = trace(@src());
defer tracy.end();
// In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of
// them, so that deleting exports is guaranteed to succeed.
try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len);
try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len);
const typed_value = decl.typed_value.most_recent.typed_value;
if (decl.link.local_sym_index == 0) return;
const decl_sym = self.local_symbols.items[decl.link.local_sym_index];
@ -1787,9 +2106,31 @@ pub const File = struct {
}
}
/// Must be called only after a successful call to `updateDecl`.
pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Decl) !void {
const tracy = trace(@src());
defer tracy.end();
const scope_file = decl.scope.cast(Module.Scope.File).?;
const tree = scope_file.contents.tree;
const file_ast_decls = tree.root_node.decls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
const block = fn_proto.body().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
const casted_line_off = @intCast(u28, line_delta);
const shdr = &self.sections.items[self.debug_line_section_index.?];
const file_pos = shdr.sh_offset + decl.fn_link.off + self.getRelocDbgLineOff();
var data: [4]u8 = undefined;
leb128.writeUnsignedFixed(4, &data, casted_line_off);
try self.file.?.pwriteAll(&data, file_pos);
}
pub fn deleteExport(self: *Elf, exp: Export) void {
const sym_index = exp.sym_index orelse return;
self.global_symbol_free_list.appendAssumeCapacity(sym_index);
self.global_symbol_free_list.append(self.allocator, sym_index) catch {};
self.global_symbols.items[sym_index].st_info = 0;
}
@ -1817,7 +2158,6 @@ pub const File = struct {
fn writeSectHeader(self: *Elf, index: usize) !void {
const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const offset = self.sections.items[index].sh_offset;
switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
32 => {
var shdr: [1]elf.Elf32_Shdr = undefined;
@ -1825,6 +2165,7 @@ pub const File = struct {
if (foreign_endian) {
bswapAllFields(elf.Elf32_Shdr, &shdr[0]);
}
const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);
return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
},
64 => {
@ -1832,6 +2173,7 @@ pub const File = struct {
if (foreign_endian) {
bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
}
const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);
return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
},
else => return error.UnsupportedArchitecture,
@ -1841,10 +2183,7 @@ pub const File = struct {
fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
const shdr = &self.sections.items[self.got_section_index.?];
const phdr = &self.program_headers.items[self.phdr_got_index.?];
const entry_size: u16 = switch (self.ptr_width) {
.p32 => 4,
.p64 => 8,
};
const entry_size: u16 = self.ptrWidthBytes();
if (self.offset_table_count_dirty) {
// TODO Also detect virtual address collisions.
const allocated_size = self.allocatedSize(shdr.sh_offset);
@ -1987,6 +2326,122 @@ pub const File = struct {
},
}
}
fn ptrWidthBytes(self: Elf) u8 {
return switch (self.ptr_width) {
.p32 => 4,
.p64 => 8,
};
}
/// The reloc offset for the virtual address of a function in its Line Number Program.
/// Size is a virtual address integer.
const dbg_line_vaddr_reloc_index = 3;
/// The reloc offset for the line offset of a function from the previous function's line.
/// It's a fixed-size 4-byte ULEB128.
fn getRelocDbgLineOff(self: Elf) usize {
return dbg_line_vaddr_reloc_index + self.ptrWidthBytes() + 1;
}
fn getRelocDbgFileIndex(self: Elf) usize {
return self.getRelocDbgLineOff() + 5;
}
fn dbgLineNeededHeaderBytes(self: Elf) u32 {
const directory_entry_format_count = 1;
const file_name_entry_format_count = 1;
const directory_count = 1;
const file_name_count = 1;
return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 +
directory_count * 8 + file_name_count * 8 +
// These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like
// because of a workaround for readelf and gdb failing to understand DWARFv5 correctly.
self.base.options.root_pkg.root_src_dir_path.len +
self.base.options.root_pkg.root_src_path.len);
}
/// Writes to the file a buffer, prefixed and suffixed by the specified number of
/// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes
/// are less than 126,976 bytes (if this limit is ever reached, this function can be
/// improved to make more than one pwritev call, or the limit can be raised by a fixed
/// amount by increasing the length of `vecs`).
fn pwriteWithNops(
self: *Elf,
prev_padding_size: usize,
buf: []const u8,
next_padding_size: usize,
offset: usize,
) !void {
const page_of_nops = [1]u8{DW.LNS_negate_stmt} ** 4096;
const three_byte_nop = [3]u8{DW.LNS_advance_pc, 0b1000_0000, 0};
var vecs: [32]std.os.iovec_const = undefined;
var vec_index: usize = 0;
{
var padding_left = prev_padding_size;
if (padding_left % 2 != 0) {
vecs[vec_index] = .{
.iov_base = &three_byte_nop,
.iov_len = three_byte_nop.len,
};
vec_index += 1;
padding_left -= three_byte_nop.len;
}
while (padding_left > page_of_nops.len) {
vecs[vec_index] = .{
.iov_base = &page_of_nops,
.iov_len = page_of_nops.len,
};
vec_index += 1;
padding_left -= page_of_nops.len;
}
if (padding_left > 0) {
vecs[vec_index] = .{
.iov_base = &page_of_nops,
.iov_len = padding_left,
};
vec_index += 1;
}
}
vecs[vec_index] = .{
.iov_base = buf.ptr,
.iov_len = buf.len,
};
vec_index += 1;
{
var padding_left = next_padding_size;
if (padding_left % 2 != 0) {
vecs[vec_index] = .{
.iov_base = &three_byte_nop,
.iov_len = three_byte_nop.len,
};
vec_index += 1;
padding_left -= three_byte_nop.len;
}
while (padding_left > page_of_nops.len) {
vecs[vec_index] = .{
.iov_base = &page_of_nops,
.iov_len = page_of_nops.len,
};
vec_index += 1;
padding_left -= page_of_nops.len;
}
if (padding_left > 0) {
vecs[vec_index] = .{
.iov_base = &page_of_nops,
.iov_len = padding_left,
};
vec_index += 1;
}
}
try self.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size);
}
const min_nop_size = 2;
};
};

View File

@ -10,9 +10,7 @@ const Module = @import("Module.zig");
const link = @import("link.zig");
const Package = @import("Package.zig");
const zir = @import("zir.zig");
// TODO Improve async I/O enough that we feel comfortable doing this.
//pub const io_mode = .evented;
const build_options = @import("build_options");
pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
@ -47,18 +45,16 @@ pub fn log(
if (@enumToInt(level) > @enumToInt(std.log.level))
return;
const scope_prefix = "(" ++ switch (scope) {
// Uncomment to hide logs
//.compiler,
.module,
.liveness,
.link,
=> return,
const scope_name = @tagName(scope);
const ok = comptime for (build_options.log_scopes) |log_scope| {
if (mem.eql(u8, log_scope, scope_name))
break true;
} else false;
else => @tagName(scope),
} ++ "): ";
if (!ok)
return;
const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
const prefix = "[" ++ @tagName(level) ++ "] " ++ "(" ++ @tagName(scope) ++ "): ";
// Print the message to stderr, silently ignoring any errors
std.debug.print(prefix ++ format, args);

View File

@ -107,6 +107,8 @@ pub const Inst = struct {
condbr,
/// Special case, has no textual representation.
@"const",
/// Declares the beginning of a statement. Used for debug info.
dbg_stmt,
/// Represents a pointer to a global decl by name.
declref,
/// Represents a pointer to a global decl by string name.
@ -211,6 +213,7 @@ pub const Inst = struct {
return switch (tag) {
.arg,
.breakpoint,
.dbg_stmt,
.returnvoid,
.alloc_inferred,
.ret_ptr,
@ -324,6 +327,7 @@ pub const Inst = struct {
.coerce_result_block_ptr,
.coerce_to_ptr_elem,
.@"const",
.dbg_stmt,
.declref,
.declref_str,
.declval,
@ -1843,6 +1847,7 @@ const EmitZIR = struct {
.breakpoint => try self.emitNoOp(inst.src, .breakpoint),
.unreach => try self.emitNoOp(inst.src, .@"unreachable"),
.retvoid => try self.emitNoOp(inst.src, .returnvoid),
.dbg_stmt => try self.emitNoOp(inst.src, .dbg_stmt),
.not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot),
.ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"),

View File

@ -41,6 +41,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?),
.compileerror => return analyzeInstCompileError(mod, scope, old_inst.castTag(.compileerror).?),
.@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?),
.dbg_stmt => return analyzeInstDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?),
.declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?),
.declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?),
.declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?),
@ -487,6 +488,11 @@ fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid)
return analyzeBreak(mod, scope, inst.base.src, block, void_inst);
}
fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
const b = try mod.requireRuntimeBlock(scope, inst.base.src);
return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt);
}
fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst {
const decl_name = try resolveConstString(mod, scope, inst.positionals.name);
return mod.analyzeDeclRefByName(scope, inst.base.src, decl_name);