commit 3c3f5dfc4fc87fabe8b9a64a7589d9af09f64d7f (tree)
parent 84142ad56bb22105f23a357201973a91a90e21b9
Author: kcbanner <kcbanner@gmail.com>
Date: Fri, 5 Jun 2026 01:55:34 -0400
Coff: Load object inputs
- Create globals for all symbols (images)
- Copy the object into a new member and index it's symbols (archives)
- Support build-lib with no zcu (ie. a c file or obj inputs)
Diffstat:
| M | src/link/Coff.zig | | | 292 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
1 file changed, 254 insertions(+), 38 deletions(-)
diff --git a/src/link/Coff.zig b/src/link/Coff.zig
@@ -334,7 +334,10 @@ pub const Member = struct {
kind: Kind,
header_ni: MappedFile.Node.Index,
content_ni: MappedFile.Node.Index,
- first_linker_indices: std.AutoArrayHashMapUnmanaged(Symbol.Index, FirstLinkerIndex),
+ first_linker_indices: std.AutoArrayHashMapUnmanaged(struct {
+ mi: Member.Index,
+ name: String,
+ }, FirstLinkerIndex),
pub const Kind = enum {
first_linker,
@@ -670,7 +673,7 @@ pub const Symbol = struct {
section_number: SectionNumber,
sti: SymbolTable.Index,
gmi: Node.GlobalMapIndex,
- unused1: u16 = 0,
+ unused0: u16 = 0,
pub const SectionNumber = enum(i16) {
UNDEFINED = 0,
@@ -1319,8 +1322,11 @@ fn initHeaders(
break :parent zcu_member.content_ni;
}
- assert(Node.known.zcu_member_header == try coff.mf.addLastChildNode(gpa, Node.known.file, .{}));
- assert(Node.known.zcu_member == try coff.mf.addLastChildNode(gpa, Node.known.file, .{}));
+ // These placeholder nodes are placed before the first member - if there are
+ // no other members then the last linker member (longnames) needs to expand
+ // to fill the padding at the end of the file.
+ assert(Node.known.zcu_member_header == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
+ assert(Node.known.zcu_member == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
coff.nodes.appendAssumeCapacity(.placeholder);
coff.nodes.appendAssumeCapacity(.placeholder);
@@ -1339,7 +1345,7 @@ fn initHeaders(
const zcu_coff_parent_ni = opt_zcu_coff_parent_ni orelse {
// If we're not generating any code, no more known nodes are used
while (coff.nodes.len < Node.known_count) {
- _ = try coff.mf.addLastChildNode(gpa, Node.known.file, .{});
+ _ = try coff.mf.addNodeAfter(gpa, Node.known.header, .{});
coff.nodes.appendAssumeCapacity(.placeholder);
}
@@ -1976,11 +1982,20 @@ fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional {
return (try coff.getOrPutString(string orelse return .none)).toOptional();
}
+/// `len` does not include null terminators
fn ensureUnusedStringCapacity(coff: *Coff, len: usize) !void {
const gpa = coff.base.comp.gpa;
try coff.strings.ensureUnusedCapacityContext(gpa, 1, .{ .bytes = &coff.string_bytes });
try coff.string_bytes.ensureUnusedCapacity(gpa, len + 1);
}
+
+/// `total_len` includes null terminators
+fn ensureManyUnusedStringCapacity(coff: *Coff, num_strings: u32, total_len: usize) !void {
+ const gpa = coff.base.comp.gpa;
+ try coff.strings.ensureUnusedCapacityContext(gpa, num_strings, .{ .bytes = &coff.string_bytes });
+ try coff.string_bytes.ensureUnusedCapacity(gpa, total_len + num_strings);
+}
+
fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
const gop = coff.strings.getOrPutAssumeCapacityAdapted(
string,
@@ -1995,10 +2010,15 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
return @enumFromInt(gop.key_ptr.*);
}
-pub fn globalSymbol(coff: *Coff, opts: struct {
+const GlobalOptions = struct {
name: []const u8,
lib_name: ?[]const u8 = null,
-}) !Symbol.Index {
+};
+
+fn getOrPutGlobalSymbol(
+ coff: *Coff,
+ opts: GlobalOptions,
+) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult {
const gpa = coff.base.comp.gpa;
try coff.symbols.ensureUnusedCapacity(gpa, 1);
const sym_gop = try coff.globals.getOrPut(gpa, .{
@@ -2012,7 +2032,11 @@ pub fn globalSymbol(coff: *Coff, opts: struct {
coff.synth_prog_node.increaseEstimatedTotalItems(1);
}
- return sym_gop.value_ptr.*;
+ return sym_gop;
+}
+
+pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
+ return (try coff.getOrPutGlobalSymbol(opts)).value_ptr.*;
}
pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void {
@@ -2225,22 +2249,14 @@ fn appendMemberSymbolString(
name_slice[name.len] = 0;
}
-fn ensureMemberSymbol(
- coff: *Coff,
- name: String,
- mi: Member.Index,
- si: Symbol.Index,
-) !void {
+fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
const gpa = coff.base.comp.gpa;
const member = mi.get(coff);
assert(member.kind == .coff);
- const gop = try member.first_linker_indices.getOrPut(gpa, si);
+ const gop = try member.first_linker_indices.getOrPut(gpa, .{ .mi = mi, .name = name });
if (gop.found_existing) return;
- // TODO: Detect duplicate names (ie. a name used by a symbol in another member,
- // not the zcu since those already go through globals)
-
const mfli: Member.FirstLinkerIndex = blk: {
const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr();
const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big);
@@ -2276,14 +2292,15 @@ fn ensureMemberSymbol(
const new_header_size = old_header_size + @sizeOf(u16);
try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
- const needs_sort = if (coff.lib_string_table.items.len > 0)
+ const old_needs_sort = coff.pending_members.get(Member.Index.second) != null;
+ const needs_sort = old_needs_sort or (if (coff.lib_string_table.items.len > 0)
std.mem.lessThan(
u8,
name_slice,
coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff),
)
else
- false;
+ false);
try coff.lib_string_table.append(gpa, name);
@@ -2291,13 +2308,13 @@ fn ensureMemberSymbol(
const num_symbols_ptr: *u32 = @ptrCast(@alignCast(slice[@sizeOf(u32) + num_members * @sizeOf(u32) ..]));
coff.targetStore(num_symbols_ptr, @intFromEnum(mfli) + 1);
- if (needs_sort) {
- // The entire string table is rebuilt in flushMember after sorting
- coff.pending_members.putAssumeCapacity(Member.Index.second, {});
- } else {
+ if (!needs_sort) {
@memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
@memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
+ } else if (!old_needs_sort) {
+ // The entire string table is rebuilt in flushMember after sorting
+ coff.pending_members.putAssumeCapacity(Member.Index.second, {});
}
// Indices in this table are 1-based
@@ -2708,16 +2725,216 @@ pub fn addReloc(
target.target_relocs = ri;
}
-pub fn loadInput(coff: *Coff, input: link.Input) void {
- _ = coff;
+pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError ||
+ Io.File.Reader.Error || MappedFile.Error || error{ WriteFailed, EndOfStream, BadMagic, LinkFailure })!void {
+ const io = coff.base.comp.io;
+ var buf: [4096]u8 = undefined;
switch (input) {
- .dso_exact => unreachable,
- inline else => |i, tag| {
- log.debug("loadInput({s}: {f})", .{ @tagName(tag), i.path.fmtEscapeString() });
+ .object => |object| {
+ var fr = object.file.reader(io, &buf);
+ coff.loadObject(object.path, null, &fr, .{
+ .offset = fr.logicalPos(),
+ .size = try fr.getSize(),
+ }) catch |err| switch (err) {
+ error.ReadFailed => return fr.err.?,
+ else => |e| return e,
+ };
},
+ .archive => |archive| {
+ var fr = archive.file.reader(io, &buf);
+ coff.loadArchive(archive.path, &fr) catch |err| switch (err) {
+ error.ReadFailed => return fr.err.?,
+ else => |e| return e,
+ };
+ },
+ .res => |res| {
+ var fr = res.file.reader(io, &buf);
+ coff.loadRes(res.path, &fr) catch |err| switch (err) {
+ error.ReadFailed => return fr.err.?,
+ else => |e| return e,
+ };
+ },
+ .dso => |dso| {
+ var fr = dso.file.reader(io, &buf);
+ coff.loadDll(dso.path, &fr) catch |err| switch (err) {
+ error.ReadFailed => return fr.err.?,
+ else => |e| return e,
+ };
+ },
+ .dso_exact => unreachable,
}
}
+fn fmtArchiveNameString(archiveName: ?[]const u8) std.fmt.Alt(?[]const u8, archiveNameStringEscape) {
+ return .{ .data = archiveName };
+}
+fn archiveNameStringEscape(archiveName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void {
+ try w.print("({f})", .{std.zig.fmtString(archiveName orelse return)});
+}
+
+fn loadObject(
+ coff: *Coff,
+ path: std.Build.Cache.Path,
+ archive_name: ?[]const u8,
+ fr: *Io.File.Reader,
+ fl: MappedFile.Node.FileLocation,
+) !void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+ const target = &comp.root_mod.resolved_target.result;
+ const target_endian = coff.targetEndian();
+ const is_archive = coff.isArchive();
+
+ log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtArchiveNameString(archive_name) });
+ const header = try r.peekStruct(std.coff.Header, coff.targetEndian());
+ if (header.machine != target.toCoffMachine())
+ return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{
+ target.toCoffMachine(),
+ header.machine,
+ });
+ if (header.number_of_sections == 0) return;
+ if (@sizeOf(std.coff.Header) + header.number_of_sections * @sizeOf(std.coff.SectionHeader) > fl.size)
+ return diags.failParse(path, "invalid section table", .{});
+ const unexpected_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{
+ .RELOCS_STRIPPED,
+ .EXECUTABLE_IMAGE,
+ .AGGRESSIVE_WS_TRIM,
+ .RESERVED,
+ .BYTES_REVERSED_LO,
+ .DLL,
+ .BYTES_REVERSED_HI,
+ };
+ inline for (unexpected_flags) |flag|
+ if (@field(header.flags, @tagName(flag)))
+ return diags.failParse(path, "unexpected flag set: {t}", .{flag});
+
+ if (header.size_of_optional_header != 0)
+ return diags.failParse(path, "unexpected optional header", .{});
+
+ const symbol_table_len = header.number_of_symbols * std.coff.Symbol.sizeOf();
+ const symbol_table_end = header.pointer_to_symbol_table + symbol_table_len;
+ // String table length (which includes the length field) immediately trails the symbol table
+ if (symbol_table_end + @sizeOf(u32) > fl.size)
+ return diags.failParse(path, "bad symbol table location", .{});
+
+ try fr.seekTo(fl.offset + symbol_table_end);
+ const string_table_len = try r.peekInt(u32, target_endian);
+ if (string_table_len < @sizeOf(u32) or
+ symbol_table_end + string_table_len > fl.size)
+ return diags.failParse(path, "bad string table", .{});
+
+ const string_table = string_table: {
+ const string_table = try gpa.alloc(u8, string_table_len);
+ errdefer gpa.free(string_table);
+ try r.readSliceAll(string_table);
+ break :string_table string_table;
+ };
+ defer gpa.free(string_table);
+
+ try coff.ensureManyUnusedStringCapacity(
+ header.number_of_symbols,
+ string_table_len - @sizeOf(u32),
+ );
+
+ const mi = if (is_archive) mi: {
+ try coff.nodes.ensureUnusedCapacity(gpa, 2);
+ try coff.members.ensureUnusedCapacity(gpa, 1);
+
+ const mi = try coff.addMemberAssumeCapacity(.coff, fl.size);
+ const member = mi.get(coff);
+ try member.initHeader(coff, path.sub_path, header.time_date_stamp);
+
+ {
+ var nw: MappedFile.Node.Writer = undefined;
+ member.content_ni.writer(&coff.mf, gpa, &nw);
+ defer nw.deinit();
+
+ try fr.seekTo(fl.offset);
+ try r.streamExact(&nw.interface, fl.size);
+ }
+
+ break :mi mi;
+ } else undefined;
+
+ try fr.seekTo(fl.offset + header.pointer_to_symbol_table);
+ const symbol_size = std.coff.Symbol.sizeOf();
+
+ var symbol_ix: u32 = 0;
+ while (symbol_ix < header.number_of_symbols) {
+ const symbol: *align(2) std.coff.Symbol = @ptrCast(@alignCast(try r.take(symbol_size)));
+ defer {
+ r.toss(symbol.number_of_aux_symbols * symbol_size);
+ symbol_ix += symbol.number_of_aux_symbols + 1;
+ }
+
+ switch (symbol.section_number) {
+ .UNDEFINED, .ABSOLUTE, .DEBUG => continue,
+ else => switch (symbol.storage_class) {
+ .STATIC => if (symbol.value == 0) continue,
+ .EXTERNAL => {},
+ else => continue,
+ },
+ }
+
+ const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
+ const index = std.mem.readInt(u32, symbol.name[4..], target_endian);
+ if (index >= string_table.len)
+ return diags.failParse(path, "bad string offset for symbol {d}", .{symbol_ix});
+ break :name string_table[index..];
+ } else &symbol.name, 0);
+
+ if (is_archive) {
+ try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name));
+ continue;
+ }
+
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name });
+ if (global_gop.found_existing)
+ return diags.failParse(path, "multiple definitions of '{s}'", .{name});
+ }
+}
+
+fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+
+ log.debug("loadArchive({f})", .{path.fmtEscapeString()});
+
+ _ = gpa;
+ _ = diags;
+ _ = r;
+}
+
+fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+
+ log.debug("loadRes({f})", .{path.fmtEscapeString()});
+
+ _ = gpa;
+ _ = diags;
+ _ = r;
+}
+
+fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+
+ log.debug("loadDll({f})", .{path.fmtEscapeString()});
+
+ _ = gpa;
+ _ = diags;
+ _ = r;
+}
+
pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
_ = coff;
_ = prog_node;
@@ -3074,7 +3291,6 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
break :task;
}
if (coff.global_pending_index < coff.globals.count()) {
- const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid };
const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index);
coff.global_pending_index += 1;
const sub_prog_node = coff.synth_prog_node.start(
@@ -3082,7 +3298,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
0,
);
defer sub_prog_node.end();
- coff.flushGlobal(pt, gmi) catch |err| switch (err) {
+ coff.flushGlobal(gmi) catch |err| switch (err) {
else => |e| return e,
error.MappedFileIo => return comp.link_diags.fail(
"linker failed to lower constant: {t}",
@@ -3301,22 +3517,19 @@ fn flushUav(
si.applyLocationRelocs(coff);
}
-fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void {
- const zcu = pt.zcu;
- const comp = zcu.comp;
- const gpa = zcu.gpa;
+fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
const gn = gmi.globalName(coff);
log.debug("flushGlobal({s}, {?s}) = {d}", .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), gmi.symbol(coff) });
if (!coff.isImage()) {
const si = gmi.symbol(coff);
try coff.pendingSymbolTableEntry(si);
-
if (coff.isArchive() and si.get(coff).ni != .none)
try coff.ensureMemberSymbol(
- gn.name,
coff.getNode(Node.known.zcu_member).archive_member,
- si,
+ gn.name,
);
return;
@@ -3715,6 +3928,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
.file => {
if (coff.isArchive() and coff.members.items.len > 0) {
const last_member = coff.members.items[coff.members.items.len - 1];
+ // See .archive_member branch for reasoning
assert(Node.known.file.reverseChildren(&coff.mf).ni == last_member.content_ni);
try coff.flushResized(last_member.content_ni);
}
@@ -3867,6 +4081,8 @@ fn flushMember(coff: *Coff, mi: Member.Index) !void {
}
};
+ // TODO: Does this sort need to also sort by linker input order (if names equal)?
+
std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{
.coff = coff,
.indices = coff.secondLinkerMemberIndicesSlice(),