authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:34-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:22:41-04:00
log3c3f5dfc4fc87fabe8b9a64a7589d9af09f64d7f
tree2be2e45d72f9b2cf7c03d5aac8398f1ad9645cb0
parent84142ad56bb22105f23a357201973a91a90e21b9

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)

1 files changed, 254 insertions(+), 38 deletions(-)

src/link/Coff.zig+254-38
......@@ -334,7 +334,10 @@ pub const Member = struct {
334334 kind: Kind,
335335 header_ni: MappedFile.Node.Index,
336336 content_ni: MappedFile.Node.Index,
337 first_linker_indices: std.AutoArrayHashMapUnmanaged(Symbol.Index, FirstLinkerIndex),
337 first_linker_indices: std.AutoArrayHashMapUnmanaged(struct {
338 mi: Member.Index,
339 name: String,
340 }, FirstLinkerIndex),
338341
339342 pub const Kind = enum {
340343 first_linker,
......@@ -670,7 +673,7 @@ pub const Symbol = struct {
670673 section_number: SectionNumber,
671674 sti: SymbolTable.Index,
672675 gmi: Node.GlobalMapIndex,
673 unused1: u16 = 0,
676 unused0: u16 = 0,
674677
675678 pub const SectionNumber = enum(i16) {
676679 UNDEFINED = 0,
......@@ -1319,8 +1322,11 @@ fn initHeaders(
13191322 break :parent zcu_member.content_ni;
13201323 }
13211324
1322 assert(Node.known.zcu_member_header == try coff.mf.addLastChildNode(gpa, Node.known.file, .{}));
1323 assert(Node.known.zcu_member == try coff.mf.addLastChildNode(gpa, Node.known.file, .{}));
1325 // These placeholder nodes are placed before the first member - if there are
1326 // no other members then the last linker member (longnames) needs to expand
1327 // to fill the padding at the end of the file.
1328 assert(Node.known.zcu_member_header == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
1329 assert(Node.known.zcu_member == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
13241330 coff.nodes.appendAssumeCapacity(.placeholder);
13251331 coff.nodes.appendAssumeCapacity(.placeholder);
13261332
......@@ -1339,7 +1345,7 @@ fn initHeaders(
13391345 const zcu_coff_parent_ni = opt_zcu_coff_parent_ni orelse {
13401346 // If we're not generating any code, no more known nodes are used
13411347 while (coff.nodes.len < Node.known_count) {
1342 _ = try coff.mf.addLastChildNode(gpa, Node.known.file, .{});
1348 _ = try coff.mf.addNodeAfter(gpa, Node.known.header, .{});
13431349 coff.nodes.appendAssumeCapacity(.placeholder);
13441350 }
13451351
......@@ -1976,11 +1982,20 @@ fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional {
19761982 return (try coff.getOrPutString(string orelse return .none)).toOptional();
19771983}
19781984
1985/// `len` does not include null terminators
19791986fn ensureUnusedStringCapacity(coff: *Coff, len: usize) !void {
19801987 const gpa = coff.base.comp.gpa;
19811988 try coff.strings.ensureUnusedCapacityContext(gpa, 1, .{ .bytes = &coff.string_bytes });
19821989 try coff.string_bytes.ensureUnusedCapacity(gpa, len + 1);
19831990}
1991
1992/// `total_len` includes null terminators
1993fn ensureManyUnusedStringCapacity(coff: *Coff, num_strings: u32, total_len: usize) !void {
1994 const gpa = coff.base.comp.gpa;
1995 try coff.strings.ensureUnusedCapacityContext(gpa, num_strings, .{ .bytes = &coff.string_bytes });
1996 try coff.string_bytes.ensureUnusedCapacity(gpa, total_len + num_strings);
1997}
1998
19841999fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
19852000 const gop = coff.strings.getOrPutAssumeCapacityAdapted(
19862001 string,
......@@ -1995,10 +2010,15 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
19952010 return @enumFromInt(gop.key_ptr.*);
19962011}
19972012
1998pub fn globalSymbol(coff: *Coff, opts: struct {
2013const GlobalOptions = struct {
19992014 name: []const u8,
20002015 lib_name: ?[]const u8 = null,
2001}) !Symbol.Index {
2016};
2017
2018fn getOrPutGlobalSymbol(
2019 coff: *Coff,
2020 opts: GlobalOptions,
2021) !std.AutoArrayHashMapUnmanaged(GlobalName, Symbol.Index).GetOrPutResult {
20022022 const gpa = coff.base.comp.gpa;
20032023 try coff.symbols.ensureUnusedCapacity(gpa, 1);
20042024 const sym_gop = try coff.globals.getOrPut(gpa, .{
......@@ -2012,7 +2032,11 @@ pub fn globalSymbol(coff: *Coff, opts: struct {
20122032 coff.synth_prog_node.increaseEstimatedTotalItems(1);
20132033 }
20142034
2015 return sym_gop.value_ptr.*;
2035 return sym_gop;
2036}
2037
2038pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
2039 return (try coff.getOrPutGlobalSymbol(opts)).value_ptr.*;
20162040}
20172041
20182042pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void {
......@@ -2225,22 +2249,14 @@ fn appendMemberSymbolString(
22252249 name_slice[name.len] = 0;
22262250}
22272251
2228fn ensureMemberSymbol(
2229 coff: *Coff,
2230 name: String,
2231 mi: Member.Index,
2232 si: Symbol.Index,
2233) !void {
2252fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
22342253 const gpa = coff.base.comp.gpa;
22352254 const member = mi.get(coff);
22362255 assert(member.kind == .coff);
22372256
2238 const gop = try member.first_linker_indices.getOrPut(gpa, si);
2257 const gop = try member.first_linker_indices.getOrPut(gpa, .{ .mi = mi, .name = name });
22392258 if (gop.found_existing) return;
22402259
2241 // TODO: Detect duplicate names (ie. a name used by a symbol in another member,
2242 // not the zcu since those already go through globals)
2243
22442260 const mfli: Member.FirstLinkerIndex = blk: {
22452261 const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr();
22462262 const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big);
......@@ -2276,14 +2292,15 @@ fn ensureMemberSymbol(
22762292 const new_header_size = old_header_size + @sizeOf(u16);
22772293 try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
22782294
2279 const needs_sort = if (coff.lib_string_table.items.len > 0)
2295 const old_needs_sort = coff.pending_members.get(Member.Index.second) != null;
2296 const needs_sort = old_needs_sort or (if (coff.lib_string_table.items.len > 0)
22802297 std.mem.lessThan(
22812298 u8,
22822299 name_slice,
22832300 coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff),
22842301 )
22852302 else
2286 false;
2303 false);
22872304
22882305 try coff.lib_string_table.append(gpa, name);
22892306
......@@ -2291,13 +2308,13 @@ fn ensureMemberSymbol(
22912308 const num_symbols_ptr: *u32 = @ptrCast(@alignCast(slice[@sizeOf(u32) + num_members * @sizeOf(u32) ..]));
22922309 coff.targetStore(num_symbols_ptr, @intFromEnum(mfli) + 1);
22932310
2294 if (needs_sort) {
2295 // The entire string table is rebuilt in flushMember after sorting
2296 coff.pending_members.putAssumeCapacity(Member.Index.second, {});
2297 } else {
2311 if (!needs_sort) {
22982312 @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
22992313 @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
23002314 slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
2315 } else if (!old_needs_sort) {
2316 // The entire string table is rebuilt in flushMember after sorting
2317 coff.pending_members.putAssumeCapacity(Member.Index.second, {});
23012318 }
23022319
23032320 // Indices in this table are 1-based
......@@ -2708,16 +2725,216 @@ pub fn addReloc(
27082725 target.target_relocs = ri;
27092726}
27102727
2711pub fn loadInput(coff: *Coff, input: link.Input) void {
2712 _ = coff;
2728pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError ||
2729 Io.File.Reader.Error || MappedFile.Error || error{ WriteFailed, EndOfStream, BadMagic, LinkFailure })!void {
2730 const io = coff.base.comp.io;
2731 var buf: [4096]u8 = undefined;
27132732 switch (input) {
2714 .dso_exact => unreachable,
2715 inline else => |i, tag| {
2716 log.debug("loadInput({s}: {f})", .{ @tagName(tag), i.path.fmtEscapeString() });
2733 .object => |object| {
2734 var fr = object.file.reader(io, &buf);
2735 coff.loadObject(object.path, null, &fr, .{
2736 .offset = fr.logicalPos(),
2737 .size = try fr.getSize(),
2738 }) catch |err| switch (err) {
2739 error.ReadFailed => return fr.err.?,
2740 else => |e| return e,
2741 };
27172742 },
2743 .archive => |archive| {
2744 var fr = archive.file.reader(io, &buf);
2745 coff.loadArchive(archive.path, &fr) catch |err| switch (err) {
2746 error.ReadFailed => return fr.err.?,
2747 else => |e| return e,
2748 };
2749 },
2750 .res => |res| {
2751 var fr = res.file.reader(io, &buf);
2752 coff.loadRes(res.path, &fr) catch |err| switch (err) {
2753 error.ReadFailed => return fr.err.?,
2754 else => |e| return e,
2755 };
2756 },
2757 .dso => |dso| {
2758 var fr = dso.file.reader(io, &buf);
2759 coff.loadDll(dso.path, &fr) catch |err| switch (err) {
2760 error.ReadFailed => return fr.err.?,
2761 else => |e| return e,
2762 };
2763 },
2764 .dso_exact => unreachable,
27182765 }
27192766}
27202767
2768fn fmtArchiveNameString(archiveName: ?[]const u8) std.fmt.Alt(?[]const u8, archiveNameStringEscape) {
2769 return .{ .data = archiveName };
2770}
2771fn archiveNameStringEscape(archiveName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void {
2772 try w.print("({f})", .{std.zig.fmtString(archiveName orelse return)});
2773}
2774
2775fn loadObject(
2776 coff: *Coff,
2777 path: std.Build.Cache.Path,
2778 archive_name: ?[]const u8,
2779 fr: *Io.File.Reader,
2780 fl: MappedFile.Node.FileLocation,
2781) !void {
2782 const comp = coff.base.comp;
2783 const gpa = comp.gpa;
2784 const diags = &comp.link_diags;
2785 const r = &fr.interface;
2786 const target = &comp.root_mod.resolved_target.result;
2787 const target_endian = coff.targetEndian();
2788 const is_archive = coff.isArchive();
2789
2790 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtArchiveNameString(archive_name) });
2791 const header = try r.peekStruct(std.coff.Header, coff.targetEndian());
2792 if (header.machine != target.toCoffMachine())
2793 return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{
2794 target.toCoffMachine(),
2795 header.machine,
2796 });
2797 if (header.number_of_sections == 0) return;
2798 if (@sizeOf(std.coff.Header) + header.number_of_sections * @sizeOf(std.coff.SectionHeader) > fl.size)
2799 return diags.failParse(path, "invalid section table", .{});
2800 const unexpected_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{
2801 .RELOCS_STRIPPED,
2802 .EXECUTABLE_IMAGE,
2803 .AGGRESSIVE_WS_TRIM,
2804 .RESERVED,
2805 .BYTES_REVERSED_LO,
2806 .DLL,
2807 .BYTES_REVERSED_HI,
2808 };
2809 inline for (unexpected_flags) |flag|
2810 if (@field(header.flags, @tagName(flag)))
2811 return diags.failParse(path, "unexpected flag set: {t}", .{flag});
2812
2813 if (header.size_of_optional_header != 0)
2814 return diags.failParse(path, "unexpected optional header", .{});
2815
2816 const symbol_table_len = header.number_of_symbols * std.coff.Symbol.sizeOf();
2817 const symbol_table_end = header.pointer_to_symbol_table + symbol_table_len;
2818 // String table length (which includes the length field) immediately trails the symbol table
2819 if (symbol_table_end + @sizeOf(u32) > fl.size)
2820 return diags.failParse(path, "bad symbol table location", .{});
2821
2822 try fr.seekTo(fl.offset + symbol_table_end);
2823 const string_table_len = try r.peekInt(u32, target_endian);
2824 if (string_table_len < @sizeOf(u32) or
2825 symbol_table_end + string_table_len > fl.size)
2826 return diags.failParse(path, "bad string table", .{});
2827
2828 const string_table = string_table: {
2829 const string_table = try gpa.alloc(u8, string_table_len);
2830 errdefer gpa.free(string_table);
2831 try r.readSliceAll(string_table);
2832 break :string_table string_table;
2833 };
2834 defer gpa.free(string_table);
2835
2836 try coff.ensureManyUnusedStringCapacity(
2837 header.number_of_symbols,
2838 string_table_len - @sizeOf(u32),
2839 );
2840
2841 const mi = if (is_archive) mi: {
2842 try coff.nodes.ensureUnusedCapacity(gpa, 2);
2843 try coff.members.ensureUnusedCapacity(gpa, 1);
2844
2845 const mi = try coff.addMemberAssumeCapacity(.coff, fl.size);
2846 const member = mi.get(coff);
2847 try member.initHeader(coff, path.sub_path, header.time_date_stamp);
2848
2849 {
2850 var nw: MappedFile.Node.Writer = undefined;
2851 member.content_ni.writer(&coff.mf, gpa, &nw);
2852 defer nw.deinit();
2853
2854 try fr.seekTo(fl.offset);
2855 try r.streamExact(&nw.interface, fl.size);
2856 }
2857
2858 break :mi mi;
2859 } else undefined;
2860
2861 try fr.seekTo(fl.offset + header.pointer_to_symbol_table);
2862 const symbol_size = std.coff.Symbol.sizeOf();
2863
2864 var symbol_ix: u32 = 0;
2865 while (symbol_ix < header.number_of_symbols) {
2866 const symbol: *align(2) std.coff.Symbol = @ptrCast(@alignCast(try r.take(symbol_size)));
2867 defer {
2868 r.toss(symbol.number_of_aux_symbols * symbol_size);
2869 symbol_ix += symbol.number_of_aux_symbols + 1;
2870 }
2871
2872 switch (symbol.section_number) {
2873 .UNDEFINED, .ABSOLUTE, .DEBUG => continue,
2874 else => switch (symbol.storage_class) {
2875 .STATIC => if (symbol.value == 0) continue,
2876 .EXTERNAL => {},
2877 else => continue,
2878 },
2879 }
2880
2881 const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
2882 const index = std.mem.readInt(u32, symbol.name[4..], target_endian);
2883 if (index >= string_table.len)
2884 return diags.failParse(path, "bad string offset for symbol {d}", .{symbol_ix});
2885 break :name string_table[index..];
2886 } else &symbol.name, 0);
2887
2888 if (is_archive) {
2889 try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name));
2890 continue;
2891 }
2892
2893 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name });
2894 if (global_gop.found_existing)
2895 return diags.failParse(path, "multiple definitions of '{s}'", .{name});
2896 }
2897}
2898
2899fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
2900 const comp = coff.base.comp;
2901 const gpa = comp.gpa;
2902 const diags = &comp.link_diags;
2903 const r = &fr.interface;
2904
2905 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
2906
2907 _ = gpa;
2908 _ = diags;
2909 _ = r;
2910}
2911
2912fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
2913 const comp = coff.base.comp;
2914 const gpa = comp.gpa;
2915 const diags = &comp.link_diags;
2916 const r = &fr.interface;
2917
2918 log.debug("loadRes({f})", .{path.fmtEscapeString()});
2919
2920 _ = gpa;
2921 _ = diags;
2922 _ = r;
2923}
2924
2925fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
2926 const comp = coff.base.comp;
2927 const gpa = comp.gpa;
2928 const diags = &comp.link_diags;
2929 const r = &fr.interface;
2930
2931 log.debug("loadDll({f})", .{path.fmtEscapeString()});
2932
2933 _ = gpa;
2934 _ = diags;
2935 _ = r;
2936}
2937
27212938pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
27222939 _ = coff;
27232940 _ = prog_node;
......@@ -3074,7 +3291,6 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
30743291 break :task;
30753292 }
30763293 if (coff.global_pending_index < coff.globals.count()) {
3077 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid };
30783294 const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index);
30793295 coff.global_pending_index += 1;
30803296 const sub_prog_node = coff.synth_prog_node.start(
......@@ -3082,7 +3298,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
30823298 0,
30833299 );
30843300 defer sub_prog_node.end();
3085 coff.flushGlobal(pt, gmi) catch |err| switch (err) {
3301 coff.flushGlobal(gmi) catch |err| switch (err) {
30863302 else => |e| return e,
30873303 error.MappedFileIo => return comp.link_diags.fail(
30883304 "linker failed to lower constant: {t}",
......@@ -3301,22 +3517,19 @@ fn flushUav(
33013517 si.applyLocationRelocs(coff);
33023518}
33033519
3304fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void {
3305 const zcu = pt.zcu;
3306 const comp = zcu.comp;
3307 const gpa = zcu.gpa;
3520fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !void {
3521 const comp = coff.base.comp;
3522 const gpa = comp.gpa;
33083523 const gn = gmi.globalName(coff);
33093524 log.debug("flushGlobal({s}, {?s}) = {d}", .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), gmi.symbol(coff) });
33103525
33113526 if (!coff.isImage()) {
33123527 const si = gmi.symbol(coff);
33133528 try coff.pendingSymbolTableEntry(si);
3314
33153529 if (coff.isArchive() and si.get(coff).ni != .none)
33163530 try coff.ensureMemberSymbol(
3317 gn.name,
33183531 coff.getNode(Node.known.zcu_member).archive_member,
3319 si,
3532 gn.name,
33203533 );
33213534
33223535 return;
......@@ -3715,6 +3928,7 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
37153928 .file => {
37163929 if (coff.isArchive() and coff.members.items.len > 0) {
37173930 const last_member = coff.members.items[coff.members.items.len - 1];
3931 // See .archive_member branch for reasoning
37183932 assert(Node.known.file.reverseChildren(&coff.mf).ni == last_member.content_ni);
37193933 try coff.flushResized(last_member.content_ni);
37203934 }
......@@ -3867,6 +4081,8 @@ fn flushMember(coff: *Coff, mi: Member.Index) !void {
38674081 }
38684082 };
38694083
4084 // TODO: Does this sort need to also sort by linker input order (if names equal)?
4085
38704086 std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{
38714087 .coff = coff,
38724088 .indices = coff.secondLinkerMemberIndicesSlice(),