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:42-04:00
log652902ac7f424a719abf89a6532e207d03368728
treedaecf9d1b0a3cc0f13ae502a1be1f37e7a010372
parent6ff2b9b9137c514b2750e40bf345824461a5aabf

Coff: support loading import libraries

- Ignore duplicate inputs - Fix handling COMDAT sections with no COMDAT symbol

1 files changed, 362 insertions(+), 167 deletions(-)

src/link/Coff.zig+362-167
...@@ -31,6 +31,7 @@ long_names_table: LongNamesTable,...@@ -31,6 +31,7 @@ long_names_table: LongNamesTable,
31import_table: ImportTable,31import_table: ImportTable,
32export_table: ExportTable,32export_table: ExportTable,
33symbol_table: SymbolTable,33symbol_table: SymbolTable,
34inputs: std.ArrayHashMapUnmanaged(std.Build.Cache.Path, void, std.Build.Cache.Path.TableAdapter, false),
34input_archives: std.ArrayList(InputArchive),35input_archives: std.ArrayList(InputArchive),
35input_archive_members: std.ArrayList(InputArchive.Member),36input_archive_members: std.ArrayList(InputArchive.Member),
36input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol),37input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol),
...@@ -39,7 +40,7 @@ input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct {...@@ -39,7 +40,7 @@ input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct {
39 last: InputArchive.Member.Symbol.Index,40 last: InputArchive.Member.Symbol.Index,
40}),41}),
41pending_input: ?InputArchive.Member.Index,42pending_input: ?InputArchive.Member.Index,
42inputs: std.ArrayList(Input),43input_objects: std.ArrayList(InputObject),
43input_symbols: std.ArrayList(Symbol.Index),44input_symbols: std.ArrayList(Symbol.Index),
44input_sections: std.ArrayList(Node.InputSection),45input_sections: std.ArrayList(Node.InputSection),
45input_section_pending_index: u32,46input_section_pending_index: u32,
...@@ -254,6 +255,10 @@ pub const Node = union(enum) {...@@ -254,6 +255,10 @@ pub const Node = union(enum) {
254 return coff.globals.keys()[gmi.unwrap().?];255 return coff.globals.keys()[gmi.unwrap().?];
255 }256 }
256257
258 pub fn globalNameMutable(gmi: GlobalMapIndex, coff: *Coff) *GlobalName {
259 return &coff.globals.keys()[gmi.unwrap().?];
260 }
261
257 pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index {262 pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index {
258 return coff.globals.values()[gmi.unwrap().?];263 return coff.globals.values()[gmi.unwrap().?];
259 }264 }
...@@ -283,20 +288,8 @@ pub const Node = union(enum) {...@@ -283,20 +288,8 @@ pub const Node = union(enum) {
283 }288 }
284 };289 };
285290
286 pub const InputIndex = enum(u32) {
287 _,
288
289 pub fn path(ii: InputIndex, coff: *const Coff) std.Build.Cache.Path {
290 return coff.inputs.items[@intFromEnum(ii)].path;
291 }
292
293 pub fn memberName(ii: InputIndex, coff: *const Coff) ?[]const u8 {
294 return coff.inputs.items[@intFromEnum(ii)].member_name;
295 }
296 };
297
298 const InputSection = struct {291 const InputSection = struct {
299 ii: Node.InputIndex,292 ioi: InputObject.Index,
300 si: Symbol.Index,293 si: Symbol.Index,
301 file_location: MappedFile.Node.FileLocation,294 file_location: MappedFile.Node.FileLocation,
302 first_li: Node.InputSection.LocalIndex,295 first_li: Node.InputSection.LocalIndex,
...@@ -309,8 +302,8 @@ pub const Node = union(enum) {...@@ -309,8 +302,8 @@ pub const Node = union(enum) {
309 return &coff.input_sections.items[@intFromEnum(isi)];302 return &coff.input_sections.items[@intFromEnum(isi)];
310 }303 }
311304
312 pub fn input(isi: Index, coff: *const Coff) InputIndex {305 pub fn input(isi: Index, coff: *const Coff) InputObject.Index {
313 return coff.input_sections.items[@intFromEnum(isi)].ii;306 return coff.input_sections.items[@intFromEnum(isi)].ioi;
314 }307 }
315308
316 pub fn fileLocation(isi: Index, coff: *const Coff) MappedFile.Node.FileLocation {309 pub fn fileLocation(isi: Index, coff: *const Coff) MappedFile.Node.FileLocation {
...@@ -409,10 +402,20 @@ pub const InputArchive = struct {...@@ -409,10 +402,20 @@ pub const InputArchive = struct {
409 pub const Member = struct {402 pub const Member = struct {
410 iai: InputArchive.Index,403 iai: InputArchive.Index,
411 name: String,404 name: String,
412 // This range includes the member header405 content: union(enum) {
413 file_location: MappedFile.Node.FileLocation,406 // This range includes the member header
407 object: MappedFile.Node.FileLocation,
408 import: struct {
409 symbol_name: String,
410 lib_name: String,
411 // Either ordinal or hint, depending on value of name_type
412 import_ordinal_hint: u16,
413 type: std.coff.ImportType,
414 name_type: std.coff.ImportNameType,
415 },
416 },
414 flags: packed struct {417 flags: packed struct {
415 is_import: bool,418 // Set if an attempt was made to load this member
416 is_loaded: bool,419 is_loaded: bool,
417 },420 },
418421
...@@ -436,10 +439,22 @@ pub const InputArchive = struct {...@@ -436,10 +439,22 @@ pub const InputArchive = struct {
436 };439 };
437};440};
438441
439pub const Input = struct {442pub const InputObject = struct {
440 path: std.Build.Cache.Path,443 path: std.Build.Cache.Path,
441 member_name: ?[]const u8,444 member_name: ?[]const u8,
442 source_name: String.Optional,445 source_name: String.Optional,
446
447 pub const Index = enum(u32) {
448 _,
449
450 pub fn path(ioi: Index, coff: *const Coff) std.Build.Cache.Path {
451 return coff.input_objects.items[@intFromEnum(ioi)].path;
452 }
453
454 pub fn memberName(ioi: Index, coff: *const Coff) ?[]const u8 {
455 return coff.input_objects.items[@intFromEnum(ioi)].member_name;
456 }
457 };
443};458};
444459
445pub const Member = struct {460pub const Member = struct {
...@@ -952,7 +967,7 @@ pub const Symbol = struct {...@@ -952,7 +967,7 @@ pub const Symbol = struct {
952 };967 };
953968
954 comptime {969 comptime {
955 if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 32);970 if (!std.debug.runtime_safety) std.debug.assert(@sizeOf(Symbol) == 36);
956 }971 }
957};972};
958973
...@@ -1381,12 +1396,13 @@ fn create(...@@ -1381,12 +1396,13 @@ fn create(
1381 .pending = .empty,1396 .pending = .empty,
1382 .pending_shrink = false,1397 .pending_shrink = false,
1383 },1398 },
1399 .inputs = .empty,
1384 .input_archives = .empty,1400 .input_archives = .empty,
1385 .input_archive_members = .empty,1401 .input_archive_members = .empty,
1386 .input_archive_symbols = .empty,1402 .input_archive_symbols = .empty,
1387 .input_archive_symbol_indices = .empty,1403 .input_archive_symbol_indices = .empty,
1388 .pending_input = null,1404 .pending_input = null,
1389 .inputs = .empty,1405 .input_objects = .empty,
1390 .input_symbols = .empty,1406 .input_symbols = .empty,
1391 .input_sections = .empty,1407 .input_sections = .empty,
1392 .input_section_pending_index = 0,1408 .input_section_pending_index = 0,
...@@ -1447,11 +1463,12 @@ pub fn deinit(coff: *Coff) void {...@@ -1447,11 +1463,12 @@ pub fn deinit(coff: *Coff) void {
1447 coff.export_table.entries.deinit(gpa);1463 coff.export_table.entries.deinit(gpa);
1448 coff.symbol_table.strings.deinit(gpa);1464 coff.symbol_table.strings.deinit(gpa);
1449 coff.symbol_table.pending.deinit(gpa);1465 coff.symbol_table.pending.deinit(gpa);
1466 coff.inputs.deinit(gpa);
1450 coff.input_archives.deinit(gpa);1467 coff.input_archives.deinit(gpa);
1451 coff.input_archive_members.deinit(gpa);1468 coff.input_archive_members.deinit(gpa);
1452 coff.input_archive_symbols.deinit(gpa);1469 coff.input_archive_symbols.deinit(gpa);
1453 coff.input_archive_symbol_indices.deinit(gpa);1470 coff.input_archive_symbol_indices.deinit(gpa);
1454 coff.inputs.deinit(gpa);1471 coff.input_objects.deinit(gpa);
1455 coff.input_symbols.deinit(gpa);1472 coff.input_symbols.deinit(gpa);
1456 coff.input_sections.deinit(gpa);1473 coff.input_sections.deinit(gpa);
1457 coff.strings.deinit(gpa);1474 coff.strings.deinit(gpa);
...@@ -2772,23 +2789,28 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void...@@ -2772,23 +2789,28 @@ fn flushSymbolTableEntry(coff: *Coff, si: Symbol.Index, pt: Zcu.PerThread) !void
27722789
2773fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void {2790fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void {
2774 const member = iami.member(coff);2791 const member = iami.member(coff);
2775 if (member.file_location.size == 0) return;
2776 assert(!member.flags.is_loaded);2792 assert(!member.flags.is_loaded);
2777 defer member.flags.is_loaded = true;2793 defer member.flags.is_loaded = true;
2778 const comp = coff.base.comp;2794 switch (member.content) {
2779 const io = comp.io;2795 .import => unreachable,
2780 const path = member.iai.path(coff);2796 .object => |file_location| {
2781 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});2797 if (file_location.size == 0) return;
2782 defer file.close(io);2798 const comp = coff.base.comp;
2783 var buffer: [4096]u8 = undefined;2799 const io = comp.io;
2784 var fr = file.reader(io, &buffer);2800 const path = member.iai.path(coff);
2785 const offset = member.file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader);2801 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2786 try fr.seekTo(offset);2802 defer file.close(io);
2787 log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) });2803 var buffer: [4096]u8 = undefined;
2788 try coff.loadObject(path, member.name.toSlice(coff), &fr, .{2804 var fr = file.reader(io, &buffer);
2789 .offset = offset,2805 const offset = file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader);
2790 .size = member.file_location.size,2806 try fr.seekTo(offset);
2791 });2807 log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) });
2808 try coff.loadObject(path, member.name.toSlice(coff), &fr, .{
2809 .offset = offset,
2810 .size = file_location.size,
2811 });
2812 },
2813 }
2792}2814}
27932815
2794fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {2816fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {
...@@ -2797,8 +2819,8 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {...@@ -2797,8 +2819,8 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {
2797 const comp = coff.base.comp;2819 const comp = coff.base.comp;
2798 const io = comp.io;2820 const io = comp.io;
2799 const gpa = comp.gpa;2821 const gpa = comp.gpa;
2800 const ii = isi.input(coff);2822 const ioi = isi.input(coff);
2801 const path = ii.path(coff);2823 const path = ioi.path(coff);
2802 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});2824 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2803 defer file.close(io);2825 defer file.close(io);
2804 var fr = file.reader(io, &.{});2826 var fr = file.reader(io, &.{});
...@@ -2809,7 +2831,7 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {...@@ -2809,7 +2831,7 @@ fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {
2809 defer nw.deinit();2831 defer nw.deinit();
2810 log.debug("flushInputSection({f}{f}, {s})", .{2832 log.debug("flushInputSection({f}{f}, {s})", .{
2811 path,2833 path,
2812 fmtMemberNameString(ii.memberName(coff)),2834 fmtMemberNameString(ioi.memberName(coff)),
2813 isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff),2835 isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff),
2814 });2836 });
2815 if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)2837 if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)
...@@ -3216,7 +3238,14 @@ pub fn addReloc(...@@ -3216,7 +3238,14 @@ pub fn addReloc(
32163238
3217pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError ||3239pub fn loadInput(coff: *Coff, input: link.Input) (Io.File.Reader.SizeError ||
3218 Io.File.Reader.Error || MappedFile.Error || error{ WriteFailed, EndOfStream, BadMagic, LinkFailure })!void {3240 Io.File.Reader.Error || MappedFile.Error || error{ WriteFailed, EndOfStream, BadMagic, LinkFailure })!void {
3219 const io = coff.base.comp.io;3241 const comp = coff.base.comp;
3242 const io = comp.io;
3243
3244 const path = input.path() orelse unreachable;
3245 const gop = try coff.inputs.getOrPut(comp.gpa, path);
3246 if (gop.found_existing) return;
3247 errdefer _ = coff.inputs.swapRemove(path);
3248
3220 var buf: [4096]u8 = undefined;3249 var buf: [4096]u8 = undefined;
3221 switch (input) {3250 switch (input) {
3222 .object => |object| {3251 .object => |object| {
...@@ -3340,9 +3369,9 @@ fn loadObject(...@@ -3340,9 +3369,9 @@ fn loadObject(
3340 symbol_table_end + string_table_len > fl.size)3369 symbol_table_end + string_table_len > fl.size)
3341 return diags.failParse(path, "bad string table", .{});3370 return diags.failParse(path, "bad string table", .{});
33423371
3343 const ii: Node.InputIndex = @enumFromInt(coff.inputs.items.len);3372 const ioi: InputObject.Index = @enumFromInt(coff.input_objects.items.len);
3344 try coff.inputs.ensureUnusedCapacity(gpa, 1);3373 try coff.input_objects.ensureUnusedCapacity(gpa, 1);
3345 const input = coff.inputs.addOneAssumeCapacity();3374 const input = coff.input_objects.addOneAssumeCapacity();
3346 input.* = .{3375 input.* = .{
3347 .path = path,3376 .path = path,
3348 .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null,3377 .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null,
...@@ -3743,8 +3772,12 @@ fn loadObject(...@@ -3743,8 +3772,12 @@ fn loadObject(
3743 else => |e| return e,3772 else => |e| return e,
3744 }) |arg| {3773 }) |arg| {
3745 // Microsoft tools emit 3 space characters into this section even with /Zl3774 // Microsoft tools emit 3 space characters into this section even with /Zl
3746 if (arg.len > 0)3775 if (arg.len == 0) continue;
3747 return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg});3776
3777 if (std.mem.cutPrefix(u8, arg, "-exclude-symbols:")) |rest| {
3778 // TODO: When implementing mingw auto-exports, use this to not export this symbol
3779 _ = rest;
3780 } else return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg});
3748 }3781 }
3749 }3782 }
37503783
...@@ -3784,29 +3817,37 @@ fn loadObject(...@@ -3784,29 +3817,37 @@ fn loadObject(
3784 };3817 };
3785 },3818 },
3786 else => |comdat| {3819 else => |comdat| {
3787 const psi = section.comdat_psi.unwrap() orelse3820 const psi = section.comdat_psi.unwrap() orelse section.psi.unwrap().?;
3788 return diags.failParse(
3789 path,
3790 "COMDAT section symbol 0x{x} had no COMDAT symbol",
3791 .{pending_symbols.keys()[section.psi.unwrap().?]},
3792 );
3793
3794 const symbol = &pending_symbols.values()[psi];3821 const symbol = &pending_symbols.values()[psi];
3795 switch (symbol.value) {3822 const si = existing: switch (symbol.value) {
3796 .section, .weak_external => unreachable,3823 .weak_external => unreachable,
3797 .static => break :comdat .include,3824 .static => break :comdat .include,
3798 else => {},3825 .section => {
3799 }3826 assert(section.comdat_psi == .none);
3827 if (coff.object_section_table.get(section.name)) |si|
3828 break :existing si
3829 else if (coff.pseudo_section_table.get(section.name)) |si|
3830 break :existing si
3831 else if (coff.section_table.get(section.name)) |s|
3832 break :existing s.si
3833 else
3834 break :comdat .include;
3835 },
3836 else => {
3837 const global_gop = try coff.getOrPutGlobalSymbol(.{
3838 .name = symbol.name.toSlice(coff),
3839 .lib_name = null,
3840 });
3841 if (!global_gop.found_existing) {
3842 symbol.si = global_gop.value_ptr.*;
3843 break :comdat .include;
3844 }
38003845
3801 // TODO: Do we need to use lib_name here?3846 break :existing global_gop.value_ptr.*;
3802 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff), .lib_name = null });3847 },
3803 if (!global_gop.found_existing) {3848 };
3804 symbol.si = global_gop.value_ptr.*;
3805 break :comdat .include;
3806 }
38073849
3808 const index = pending_symbols.keys()[psi];3850 const index = pending_symbols.keys()[psi];
3809 const si = global_gop.value_ptr.*;
3810 switch (comdat) {3851 switch (comdat) {
3811 .NODUPLICATES => return coff.failMultipleDefinitions(3852 .NODUPLICATES => return coff.failMultipleDefinitions(
3812 path,3853 path,
...@@ -3816,12 +3857,17 @@ fn loadObject(...@@ -3816,12 +3857,17 @@ fn loadObject(
3816 si,3857 si,
3817 .duplicate,3858 .duplicate,
3818 ),3859 ),
3819 .ANY => break :comdat .skip,3860 .ANY => {
3861 symbol.si = si;
3862 break :comdat .skip;
3863 },
3820 .SAME_SIZE => {3864 .SAME_SIZE => {
3821 // TODO: Verify that this node isn't resized after creation3865 // TODO: Verify that this node isn't resized after creation
3822 _, const size = si.get(coff).ni.location(&coff.mf).resolve(&coff.mf);3866 _, const size = si.get(coff).ni.location(&coff.mf).resolve(&coff.mf);
3823 if (size == section.header.size_of_raw_data)3867 if (size == section.header.size_of_raw_data) {
3868 symbol.si = si;
3824 break :comdat .skip;3869 break :comdat .skip;
3870 }
38253871
3826 return coff.failMultipleDefinitions(3872 return coff.failMultipleDefinitions(
3827 path,3873 path,
...@@ -3840,8 +3886,10 @@ fn loadObject(...@@ -3840,8 +3886,10 @@ fn loadObject(
3840 else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.slice(&coff.mf)),3886 else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.slice(&coff.mf)),
3841 };3887 };
38423888
3843 if (existing_crc == section.comdat_crc)3889 if (existing_crc == section.comdat_crc) {
3890 symbol.si = si;
3844 break :comdat .skip;3891 break :comdat .skip;
3892 }
38453893
3846 return coff.failMultipleDefinitions(3894 return coff.failMultipleDefinitions(
3847 path,3895 path,
...@@ -3876,7 +3924,16 @@ fn loadObject(...@@ -3876,7 +3924,16 @@ fn loadObject(
3876 continue :comdat root_result;3924 continue :comdat root_result;
3877 },3925 },
3878 .include => {},3926 .include => {},
3879 .skip => continue,3927 .skip => {
3928 assert(switch (section.comdat) {
3929 .NONE, .ASSOCIATIVE => true,
3930 else => if (section.comdat_psi.unwrap()) |psi|
3931 pending_symbols.values()[psi].si != .null
3932 else
3933 pending_symbols.values()[section.psi.unwrap().?].si != .null,
3934 });
3935 continue;
3936 },
3880 .pending => unreachable,3937 .pending => unreachable,
3881 }3938 }
38823939
...@@ -3915,7 +3972,7 @@ fn loadObject(...@@ -3915,7 +3972,7 @@ fn loadObject(
3915 sym.section_number = section.parent_si.get(coff).section_number;3972 sym.section_number = section.parent_si.get(coff).section_number;
39163973
3917 coff.input_sections.addOneAssumeCapacity().* = .{3974 coff.input_sections.addOneAssumeCapacity().* = .{
3918 .ii = ii,3975 .ioi = ioi,
3919 .si = section.si,3976 .si = section.si,
3920 .file_location = .{3977 .file_location = .{
3921 .offset = fl.offset + section.header.pointer_to_raw_data,3978 .offset = fl.offset + section.header.pointer_to_raw_data,
...@@ -3987,8 +4044,7 @@ fn loadObject(...@@ -3987,8 +4044,7 @@ fn loadObject(
3987 symbol.si = coff.addSymbolAssumeCapacity();4044 symbol.si = coff.addSymbolAssumeCapacity();
3988 },4045 },
3989 .external => {4046 .external => {
3990 // COMDAT symbols were created when enumerating the sections4047 // TODO: Assert this is not the comdat leader
3991 assert(section.comdat == .NONE);
3992 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });4048 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
3993 symbol.si = global_gop.value_ptr.*;4049 symbol.si = global_gop.value_ptr.*;
39944050
...@@ -4086,20 +4142,26 @@ fn loadObject(...@@ -4086,20 +4142,26 @@ fn loadObject(
40864142
4087 try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);4143 try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
4088 var prev_sn: Symbol.SectionNumber = .UNDEFINED;4144 var prev_sn: Symbol.SectionNumber = .UNDEFINED;
4145 var include_section = true;
4089 for (pending_symbols.values()) |symbol| {4146 for (pending_symbols.values()) |symbol| {
4090 // The symbol may have not been included, or it's an undefined external4147 // The symbol may have not been included, or it's an undefined external
4091 if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue;4148 if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue;
4092 assert(coff.getNode(symbol.si.get(coff).ni) == .input_section);
40934149
4094 if (prev_sn != symbol.section_number) {4150 if (prev_sn != symbol.section_number) {
4095 prev_sn = symbol.section_number;4151 prev_sn = symbol.section_number;
40964152
4097 const section = &sections[symbol.section_number.toIndex()];4153 const section = &sections[symbol.section_number.toIndex()];
4098 const isi = coff.getNode(section.si.get(coff).ni).input_section;4154 include_section = section.comdat_result == .include;
4099 isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len);4155 if (include_section) {
4156 const isi = coff.getNode(section.si.get(coff).ni).input_section;
4157 isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len);
4158 }
4100 }4159 }
41014160
4102 coff.input_symbols.addOneAssumeCapacity().* = symbol.si;4161 if (include_section) {
4162 assert(coff.getNode(symbol.si.get(coff).ni) == .input_section);
4163 coff.input_symbols.addOneAssumeCapacity().* = symbol.si;
4164 }
4103 }4165 }
4104}4166}
41054167
...@@ -4123,16 +4185,15 @@ fn failMultipleDefinitions(...@@ -4123,16 +4185,15 @@ fn failMultipleDefinitions(
41234185
4124 switch (coff.getNode(existing_si.get(coff).ni)) {4186 switch (coff.getNode(existing_si.get(coff).ni)) {
4125 .input_section => |isi| {4187 .input_section => |isi| {
4126 const other_ii = isi.input(coff);4188 const other_ioi = isi.input(coff);
4127 err.addNote("first seen in input '{f}{f}'", .{4189 err.addNote("first seen in input '{f}{f}'", .{
4128 other_ii.path(coff).fmtEscapeString(),4190 other_ioi.path(coff).fmtEscapeString(),
4129 fmtMemberNameString(other_ii.memberName(coff)),4191 fmtMemberNameString(other_ioi.memberName(coff)),
4130 });4192 });
4131 },4193 },
4132 .nav, .uav => err.addNote("first seen in module '{s}'", .{4194 .nav, .uav => err.addNote("first seen in module '{s}'", .{
4133 coff.base.comp.zcu.?.root_mod.fully_qualified_name,4195 coff.base.comp.zcu.?.root_mod.fully_qualified_name,
4134 }),4196 }),
4135 //else => |_, tag| err.addNote("TODO multiple def for {t}", .{tag}),
4136 else => unreachable,4197 else => unreachable,
4137 }4198 }
41384199
...@@ -4158,6 +4219,7 @@ const ArchiveMemberHeader = struct {...@@ -4158,6 +4219,7 @@ const ArchiveMemberHeader = struct {
4158 size: u34,4219 size: u34,
4159};4220};
41604221
4222/// Return value lifetime is that of `header`
4161fn parseArchiveMemberHeader(4223fn parseArchiveMemberHeader(
4162 diags: *link.Diags,4224 diags: *link.Diags,
4163 path: std.Build.Cache.Path,4225 path: std.Build.Cache.Path,
...@@ -4346,10 +4408,14 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo...@@ -4346,10 +4408,14 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo
4346 coff.input_archive_members.addOneAssumeCapacity().* = .{4408 coff.input_archive_members.addOneAssumeCapacity().* = .{
4347 .iai = iai,4409 .iai = iai,
4348 .name = undefined,4410 .name = undefined,
4349 .flags = undefined,4411 .content = .{
4350 .file_location = .{4412 .object = .{
4351 .offset = member_offset,4413 .offset = member_offset,
4352 .size = undefined,4414 .size = undefined,
4415 },
4416 },
4417 .flags = .{
4418 .is_loaded = false,
4353 },4419 },
4354 };4420 };
43554421
...@@ -4394,9 +4460,9 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo...@@ -4394,9 +4460,9 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo
4394 else => {},4460 else => {},
4395 };4461 };
43964462
4397 // Validate / read names and sizes of all the referenced members4463 // Validate / read names and sizes of all the referenced members, enumerate imports
4398 for (coff.input_archive_members.items[first_iami..]) |*member| {4464 for (coff.input_archive_members.items[first_iami..]) |*member| {
4399 try fr.seekTo(member.file_location.offset);4465 try fr.seekTo(member.content.object.offset);
44004466
4401 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);4467 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
4402 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);4468 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
...@@ -4405,28 +4471,74 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo...@@ -4405,28 +4471,74 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo
4405 member.name = coff.getOrPutStringAssumeCapacity(res.name);4471 member.name = coff.getOrPutStringAssumeCapacity(res.name);
44064472
4407 const member_sig = try r.peek(4);4473 const member_sig = try r.peek(4);
4408 const machine = std.mem.readInt(u16, member_sig[0..2], target_endian);4474 const machine: std.coff.IMAGE.FILE.MACHINE =
4475 @enumFromInt(std.mem.readInt(u16, member_sig[0..2], target_endian));
4409 const sig = std.mem.readInt(u16, member_sig[2..4], target_endian);4476 const sig = std.mem.readInt(u16, member_sig[2..4], target_endian);
4410 member.flags = .{
4411 .is_import = machine == @intFromEnum(std.coff.IMAGE.FILE.MACHINE.UNKNOWN) and sig == 0xffff,
4412 .is_loaded = false,
4413 };
4414 member.file_location.size = res.size;
44154477
4416 log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{4478 log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{
4417 res.name,4479 res.name,
4418 member.file_location.offset,4480 member.content.object.offset,
4419 member.file_location.size,4481 res.size,
4420 });4482 });
44214483
4422 if (member.flags.is_import) {4484 const expected_machine = comp.root_mod.resolved_target.result.toCoffMachine();
4423 const import_header = try r.peekStruct(std.coff.ImportHeader, target_endian);4485 if (machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff) {
4424 // TODO: Validate import table header fields4486 const import_header = try r.takeStruct(std.coff.ImportHeader, target_endian);
4425 // TODO: Use this result in flushGlobal4487 const strings = r.take(import_header.size_of_data) catch |err| switch (err) {
4426 return diags.failParse(path, "TODO implement parsing import headers: {t} {t}", .{4488 error.EndOfStream => return diags.failParse(path, "invalid data size in import header '{s}'", .{res.name}),
4489 else => |e| return e,
4490 };
4491
4492 var split = std.mem.splitScalar(u8, strings, 0);
4493 const symbol_name = split.next() orelse
4494 return diags.failParse(path, "invalid symbol name string in import header '{s}'", .{res.name});
4495 var lib_name = split.next() orelse
4496 return diags.failParse(path, "invalid dll name string in import header '{s}' ('{s}')", .{ res.name, symbol_name });
4497
4498 if (import_header.machine != expected_machine)
4499 return diags.failParse(path, "machine mismatch in import header '{s}' ('{s}'): expected {t}, found {t}", .{
4500 res.name,
4501 symbol_name,
4502 expected_machine,
4503 machine,
4504 });
4505
4506 const ext = ".dll";
4507 if (!std.mem.endsWith(u8, lib_name, ext))
4508 return diags.failParse(
4509 path,
4510 "unexpected extension for import '{s} ('{s}'): '{s}'",
4511 .{ res.name, symbol_name, lib_name },
4512 );
4513
4514 lib_name = lib_name[0 .. lib_name.len - ext.len];
4515 log.debug("verifyArchiveImportHeader({s}, {s}, {s}) = {t} ({t})", .{
4516 res.name,
4517 symbol_name,
4518 lib_name,
4427 import_header.types.type,4519 import_header.types.type,
4428 import_header.types.name_type,4520 import_header.types.name_type,
4429 });4521 });
4522
4523 try coff.ensureManyUnusedStringCapacity(2, strings.len - ext.len);
4524 member.content = .{
4525 .import = .{
4526 .symbol_name = coff.getOrPutStringAssumeCapacity(symbol_name),
4527 .lib_name = coff.getOrPutStringAssumeCapacity(lib_name),
4528 .import_ordinal_hint = import_header.hint,
4529 .type = import_header.types.type,
4530 .name_type = import_header.types.name_type,
4531 },
4532 };
4533 } else {
4534 member.content.object.size = res.size;
4535 if (machine != expected_machine) {
4536 return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{
4537 res.name,
4538 expected_machine,
4539 machine,
4540 });
4541 }
4430 }4542 }
4431 }4543 }
4432}4544}
...@@ -4808,18 +4920,18 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {...@@ -4808,18 +4920,18 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
4808 const loc_sym = loc_si.get(coff);4920 const loc_sym = loc_si.get(coff);
4809 switch (coff.getNode(loc_sym.ni)) {4921 switch (coff.getNode(loc_sym.ni)) {
4810 .input_section => |isi| {4922 .input_section => |isi| {
4811 const other_ii = isi.input(coff);4923 const other_ioi = isi.input(coff);
4812 if (loc_sym.gmi == .none) {4924 if (loc_sym.gmi == .none) {
4813 // TODO: We could report the name here if we interned it in loadObject4925 // TODO: We could report the name here if we interned it in loadObject
4814 err.addNote("referenced internally by input '{f}{f}'", .{4926 err.addNote("referenced internally by input '{f}{f}'", .{
4815 other_ii.path(coff).fmtEscapeString(),4927 other_ioi.path(coff).fmtEscapeString(),
4816 fmtMemberNameString(other_ii.memberName(coff)),4928 fmtMemberNameString(other_ioi.memberName(coff)),
4817 });4929 });
4818 } else {4930 } else {
4819 err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{4931 err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{
4820 loc_sym.gmi.globalName(coff).name.toSlice(coff),4932 loc_sym.gmi.globalName(coff).name.toSlice(coff),
4821 other_ii.path(coff).fmtEscapeString(),4933 other_ioi.path(coff).fmtEscapeString(),
4822 fmtMemberNameString(other_ii.memberName(coff)),4934 fmtMemberNameString(other_ioi.memberName(coff)),
4823 });4935 });
4824 }4936 }
4825 },4937 },
...@@ -5010,13 +5122,13 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {...@@ -5010,13 +5122,13 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
5010 defer sub_prog_node.end();5122 defer sub_prog_node.end();
5011 coff.flushInputSection(isi) catch |err| switch (err) {5123 coff.flushInputSection(isi) catch |err| switch (err) {
5012 else => |e| {5124 else => |e| {
5013 const ii = isi.input(coff);5125 const ioi = isi.input(coff);
5014 return comp.link_diags.fail(5126 return comp.link_diags.fail(
5015 "linker failed to read input section '{s}' from \"{f}{f}\": {t}",5127 "linker failed to read input section '{s}' from \"{f}{f}\": {t}",
5016 .{5128 .{
5017 isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff),5129 isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff),
5018 ii.path(coff).fmtEscapeString(),5130 ioi.path(coff).fmtEscapeString(),
5019 fmtMemberNameString(ii.memberName(coff)),5131 fmtMemberNameString(ioi.memberName(coff)),
5020 e,5132 e,
5021 },5133 },
5022 );5134 );
...@@ -5110,10 +5222,10 @@ fn idleProgNode(...@@ -5110,10 +5222,10 @@ fn idleProgNode(
5110 .image_section => |si| std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),5222 .image_section => |si| std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
5111 inline .pseudo_section, .object_section => |smi| smi.name(coff).toSlice(coff),5223 inline .pseudo_section, .object_section => |smi| smi.name(coff).toSlice(coff),
5112 .input_section => |isi| {5224 .input_section => |isi| {
5113 const ii = isi.input(coff);5225 const ioi = isi.input(coff);
5114 break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{5226 break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{
5115 ii.path(coff).fmtEscapeString(),5227 ioi.path(coff).fmtEscapeString(),
5116 fmtMemberNameString(ii.memberName(coff)),5228 fmtMemberNameString(ioi.memberName(coff)),
5117 coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),5229 coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),
5118 }) catch &name;5230 }) catch &name;
5119 },5231 },
...@@ -5197,7 +5309,7 @@ fn flushUav(...@@ -5197,7 +5309,7 @@ fn flushUav(
5197fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {5309fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
5198 const comp = coff.base.comp;5310 const comp = coff.base.comp;
5199 const gpa = comp.gpa;5311 const gpa = comp.gpa;
5200 const gn = gmi.globalName(coff);5312 const gn = gmi.globalNameMutable(coff);
5201 const si = gmi.symbol(coff);5313 const si = gmi.symbol(coff);
5202 const sym = si.get(coff);5314 const sym = si.get(coff);
52035315
...@@ -5217,8 +5329,142 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {...@@ -5217,8 +5329,142 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
5217 return true;5329 return true;
5218 }5330 }
52195331
5220 if (gn.lib_name.toSlice(coff)) |lib_name| {5332 const Import = struct {
5221 const name = gn.name.toSlice(coff);5333 lib_name: String,
5334 ref: union(enum) {
5335 name: struct {
5336 str: []const u8,
5337 hint: ?u16,
5338 },
5339 ordinal: u16,
5340 },
5341 };
5342
5343 const opt_import: ?Import = if (gn.lib_name == .none and sym.ni == .none) import: {
5344 switch (sym.value) {
5345 .alias_si => |alias_si| {
5346 assert(sym.section_number == .UNDEFINED);
5347 assert(sym.loc_relocs == .none);
5348
5349 const alias_sym = alias_si.get(coff);
5350 var ri = sym.target_relocs;
5351 while (ri != .none) {
5352 const reloc = ri.get(coff);
5353 assert(reloc.target == si);
5354 reloc.target = alias_si;
5355 if (reloc.next == .none) {
5356 reloc.next = alias_sym.target_relocs;
5357 if (alias_sym.target_relocs != .none)
5358 alias_sym.target_relocs.get(coff).prev = ri;
5359 }
5360 ri = reloc.next;
5361 }
5362
5363 sym.target_relocs = .none;
5364 coff.globals.values()[gmi.unwrap().?] = alias_si;
5365 alias_si.applyTargetRelocs(coff);
5366
5367 log.debug(
5368 "flushGlobal({s}, null) alias {d}->{d}",
5369 .{ gmi.globalName(coff).name.toSlice(coff), si, alias_si },
5370 );
5371 return true;
5372 },
5373 .size => {},
5374 .input_offset => unreachable,
5375 }
5376
5377 if (coff.input_archive_symbol_indices.get(gmi.globalName(coff).name)) |index| {
5378 var iter: InputArchive.Member.Symbol.Index = index.first;
5379 while (true) {
5380 const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
5381 const member = &coff.input_archive_members.items[@intFromEnum(archive_sym.iami)];
5382 if (!member.flags.is_loaded) {
5383 switch (member.content) {
5384 .import => |import| switch (import.type) {
5385 .CODE,
5386 .DATA,
5387 => {
5388 defer member.flags.is_loaded = true;
5389 // gn.lib_name = import.lib_name.toOptional();
5390 // try coff.globals.setKey(gpa, gmi.unwrap().?, gn.*);
5391
5392 // Switch this global to an import
5393 switch (import.name_type) {
5394 .NAME,
5395 .NAME_NOPREFIX,
5396 .NAME_UNDECORATE,
5397 => |tag| {
5398 var name: []const u8 = import.symbol_name.toSlice(coff);
5399 if (!(std.mem.eql(u8, name, gn.name.toSlice(coff))))
5400 return comp.link_diags.fail("import '{s}' has mismatched symbol name: '{s}'", .{
5401 import.symbol_name.toSlice(coff),
5402 gn.name.toSlice(coff),
5403 });
5404
5405 name = if (tag == .NAME) name else name: {
5406 name = std.mem.trimStart(u8, name, "?@_");
5407 if (tag == .NAME_UNDECORATE)
5408 name = std.mem.sliceTo(name, '@');
5409 break :name name;
5410 };
5411
5412 break :import .{
5413 .lib_name = import.lib_name,
5414 .ref = .{
5415 .name = .{
5416 .str = name,
5417 .hint = import.import_ordinal_hint,
5418 },
5419 },
5420 };
5421 },
5422 .ORDINAL => break :import .{
5423 .lib_name = import.lib_name,
5424 .ref = .{ .ordinal = import.import_ordinal_hint },
5425 },
5426 else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}),
5427 }
5428 },
5429 .CONST => return comp.link_diags.fail("TODO handle import type CONST", .{}),
5430 else => |t| return comp.link_diags.fail("invalid import type: {d}", .{t}),
5431 },
5432 .object => {
5433 // Try loading the input member and then retry
5434 coff.pending_input = archive_sym.iami;
5435 return false;
5436 },
5437 }
5438 }
5439
5440 if (archive_sym.next == iter) break;
5441 iter = archive_sym.next;
5442 }
5443 }
5444
5445 break :import null;
5446 } else if (gn.lib_name.unwrap()) |lib_name| .{
5447 .lib_name = lib_name,
5448 .ref = .{
5449 .name = .{
5450 .str = gn.name.toSlice(coff),
5451 .hint = null,
5452 },
5453 },
5454 } else null;
5455
5456 if (opt_import) |import| {
5457 assert(sym.ni == .none);
5458 const lib_name = import.lib_name.toSlice(coff);
5459 const name = switch (import.ref) {
5460 .name => |n| n.str,
5461 .ordinal => return comp.link_diags.fail("TODO handle imports via ordinal", .{}),
5462 };
5463
5464 log.debug("flushGlobalImport({s}, {s})", .{ name, lib_name });
5465
5466 // TODO: Handle hint
5467
5222 try coff.nodes.ensureUnusedCapacity(gpa, 4);5468 try coff.nodes.ensureUnusedCapacity(gpa, 4);
5223 try coff.symbols.ensureUnusedCapacity(gpa, 1);5469 try coff.symbols.ensureUnusedCapacity(gpa, 1);
52245470
...@@ -5371,57 +5617,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {...@@ -5371,57 +5617,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
5371 coff.nodes.appendAssumeCapacity(.{ .global = gmi });5617 coff.nodes.appendAssumeCapacity(.{ .global = gmi });
5372 sym.rva = coff.computeNodeRva(sym.ni);5618 sym.rva = coff.computeNodeRva(sym.ni);
5373 si.applyLocationRelocs(coff);5619 si.applyLocationRelocs(coff);
5374 } else if (sym.ni == .none) {
5375 switch (sym.value) {
5376 .alias_si => |alias_si| {
5377 assert(sym.section_number == .UNDEFINED);
5378 assert(sym.loc_relocs == .none);
5379
5380 const alias_sym = alias_si.get(coff);
5381 var ri = sym.target_relocs;
5382 while (ri != .none) {
5383 const reloc = ri.get(coff);
5384 assert(reloc.target == si);
5385 reloc.target = alias_si;
5386 if (reloc.next == .none) {
5387 reloc.next = alias_sym.target_relocs;
5388 if (alias_sym.target_relocs != .none)
5389 alias_sym.target_relocs.get(coff).prev = ri;
5390 }
5391 ri = reloc.next;
5392 }
5393
5394 sym.target_relocs = .none;
5395 coff.globals.values()[gmi.unwrap().?] = alias_si;
5396 alias_si.applyTargetRelocs(coff);
5397
5398 log.debug("flushGlobal({s}, {?s}) alias {d}->{d}", .{
5399 gmi.globalName(coff).name.toSlice(coff),
5400 gmi.globalName(coff).lib_name.toSlice(coff),
5401 si,
5402 alias_si,
5403 });
5404
5405 return true;
5406 },
5407 .size => {},
5408 .input_offset => unreachable,
5409 }
5410
5411 if (coff.input_archive_symbol_indices.get(gmi.globalName(coff).name)) |index| {
5412 var iter: InputArchive.Member.Symbol.Index = index.first;
5413 while (true) {
5414 const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
5415 if (!coff.input_archive_members.items[@intFromEnum(archive_sym.iami)].flags.is_loaded) {
5416 // Try loading the input member and then retry
5417 coff.pending_input = archive_sym.iami;
5418 return false;
5419 }
5420
5421 if (archive_sym.next == iter) break;
5422 iter = archive_sym.next;
5423 }
5424 }
5425 }5620 }
54265621
5427 return true;5622 return true;
...@@ -6097,10 +6292,10 @@ pub fn printNode(...@@ -6097,10 +6292,10 @@ pub fn printNode(
6097 std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),6292 std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
6098 }),6293 }),
6099 .input_section => |isi| {6294 .input_section => |isi| {
6100 const ii = isi.input(coff);6295 const ioi = isi.input(coff);
6101 try w.print("({f}{f}, {s})", .{6296 try w.print("({f}{f}, {s})", .{
6102 ii.path(coff).fmtEscapeString(),6297 ioi.path(coff).fmtEscapeString(),
6103 fmtMemberNameString(ii.memberName(coff)),6298 fmtMemberNameString(ioi.memberName(coff)),
6104 coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),6299 coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),
6105 });6300 });
6106 },6301 },