commit 6ff2b9b9137c514b2750e40bf345824461a5aabf (tree)
parent c7c1d65372019199824d5f2284a63031383f0512
Author: kcbanner <kcbanner@gmail.com>
Date: Fri, 5 Jun 2026 01:55:34 -0400
Coff: handle COMDAT sections and weak externals
- Rework object loading to handle COMDAT sections
- Add better error output for multiple definitions
- Track all symbols associated with input sections in a flat array
- Resolve weak externals to their alias if nothing defines the weak symbol
- Fixup incorrect signs of reloc addends in the logic that saves / restores them
Diffstat:
2 files changed, 752 insertions(+), 335 deletions(-)
diff --git a/lib/std/coff.zig b/lib/std/coff.zig
@@ -654,6 +654,10 @@ pub const SectionHeader = extern struct {
std.debug.assert(std.math.isPowerOfTwo(n));
return @enumFromInt(@ctz(n) + 1);
}
+
+ pub fn alignment(a: Align) ?std.mem.Alignment {
+ return .fromByteUnitsOptional(a.toByteUnits() orelse null);
+ }
};
};
};
diff --git a/src/link/Coff.zig b/src/link/Coff.zig
@@ -40,9 +40,10 @@ input_archive_symbol_indices: std.AutoArrayHashMapUnmanaged(String, struct {
}),
pending_input: ?InputArchive.Member.Index,
inputs: std.ArrayList(Input),
-input_resolved: std.ArrayList(Symbol.Index),
+input_symbols: std.ArrayList(Symbol.Index),
input_sections: std.ArrayList(Node.InputSection),
input_section_pending_index: u32,
+inputs_complete: bool,
strings: std.HashMapUnmanaged(
u32,
void,
@@ -292,25 +293,14 @@ pub const Node = union(enum) {
pub fn memberName(ii: InputIndex, coff: *const Coff) ?[]const u8 {
return coff.inputs.items[@intFromEnum(ii)].member_name;
}
-
- pub fn firstSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index {
- return coff.inputs.items[@intFromEnum(ii)].first_si;
- }
-
- pub fn endSymbol(ii: InputIndex, coff: *const Coff) Symbol.Index {
- return coff.inputs.items[@intFromEnum(ii)].end_si;
- }
-
- pub fn firstResolvedGlobal(ii: InputIndex, coff: *const Coff) Input.ResolvedIndex {
- return coff.inputs.items[@intFromEnum(ii)].first_iri;
- }
};
const InputSection = struct {
ii: Node.InputIndex,
si: Symbol.Index,
file_location: MappedFile.Node.FileLocation,
- first_iri: Node.InputSection.ResolvedIndex,
+ first_li: Node.InputSection.LocalIndex,
+ crc: u32,
pub const Index = enum(u32) {
_,
@@ -331,12 +321,12 @@ pub const Node = union(enum) {
return coff.input_sections.items[@intFromEnum(isi)].si;
}
- pub fn firstResolvedSymbol(isi: Index, coff: *const Coff) ResolvedIndex {
- return coff.input_sections.items[@intFromEnum(isi)].first_iri;
+ pub fn firstSymbol(isi: Index, coff: *const Coff) LocalIndex {
+ return coff.input_sections.items[@intFromEnum(isi)].first_li;
}
};
- const ResolvedIndex = enum(u32) {
+ const LocalIndex = enum(u32) {
_,
};
};
@@ -449,8 +439,7 @@ pub const InputArchive = struct {
pub const Input = struct {
path: std.Build.Cache.Path,
member_name: ?[]const u8,
- first_si: Symbol.Index,
- end_si: Symbol.Index,
+ source_name: String.Optional,
};
pub const Member = struct {
@@ -814,11 +803,14 @@ pub const GlobalName = struct { name: String, lib_name: String.Optional };
pub const Symbol = struct {
ni: MappedFile.Node.Index,
rva: u32,
- value: union {
- /// For generated symbols, this is their size
- size: u32,
- /// For symbols from input sections, this is the offset within the input section
+ value: union(enum) {
+ /// For .ni == .input_section, this is the offset of this symbol within the input section
input_offset: u32,
+ /// For .ni == none and .gmi != .none, this is a weak alias
+ /// that should replace this symbol, or .null if none exists
+ alias_si: Symbol.Index,
+ /// Otherwise, this is the symbol size if known
+ size: u32,
},
/// Relocations contained within this symbol
loc_relocs: Reloc.Index,
@@ -828,7 +820,6 @@ pub const Symbol = struct {
/// Only used when outputting objects
sti: SymbolTable.Index,
gmi: Node.GlobalMapIndex,
- unused0: u16 = 0,
pub const SectionNumber = enum(i16) {
UNDEFINED = 0,
@@ -840,6 +831,13 @@ pub const Symbol = struct {
return @intCast(@intFromEnum(sn) - 1);
}
+ fn hasIndex(sn: SectionNumber) bool {
+ return switch (sn) {
+ .UNDEFINED, .ABSOLUTE, .DEBUG => false,
+ else => true,
+ };
+ }
+
pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index {
return sn.section(coff).si;
}
@@ -1021,15 +1019,21 @@ pub const Reloc = extern struct {
),
.ADDR32,
.ADDR32NB,
+ .SECREL,
+ => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
.REL32,
.REL32_1,
.REL32_2,
.REL32_3,
.REL32_4,
.REL32_5,
- .SECREL,
=> std.mem.writeInt(
- u32,
+ i32,
loc_slice[0..4],
@intCast(reloc.addend),
target_endian,
@@ -1039,16 +1043,21 @@ pub const Reloc = extern struct {
else => |kind| @panic(@tagName(kind)),
.ABSOLUTE => {},
.DIR16,
- .REL16,
=> std.mem.writeInt(
u16,
loc_slice[0..2],
@intCast(reloc.addend),
target_endian,
),
+ .REL16,
+ => std.mem.writeInt(
+ i16,
+ loc_slice[0..2],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
.DIR32,
.DIR32NB,
- .REL32,
.SECREL,
=> std.mem.writeInt(
u32,
@@ -1056,6 +1065,13 @@ pub const Reloc = extern struct {
@intCast(reloc.addend),
target_endian,
),
+ .REL32,
+ => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
},
}
@@ -1074,15 +1090,20 @@ pub const Reloc = extern struct {
)),
.ADDR32,
.ADDR32NB,
+ .SECREL,
+ => std.mem.readInt(
+ u32,
+ loc_slice[0..4],
+ target_endian,
+ ),
.REL32,
.REL32_1,
.REL32_2,
.REL32_3,
.REL32_4,
.REL32_5,
- .SECREL,
=> std.mem.readInt(
- u32,
+ i32,
loc_slice[0..4],
target_endian,
),
@@ -1091,21 +1112,31 @@ pub const Reloc = extern struct {
else => |kind| @panic(@tagName(kind)),
.ABSOLUTE => 0,
.DIR16,
- .REL16,
=> std.mem.readInt(
u16,
loc_slice[0..2],
target_endian,
),
+ .REL16,
+ => std.mem.readInt(
+ i16,
+ loc_slice[0..2],
+ target_endian,
+ ),
.DIR32,
.DIR32NB,
- .REL32,
.SECREL,
=> std.mem.readInt(
u32,
loc_slice[0..4],
target_endian,
),
+ .REL32,
+ => std.mem.readInt(
+ i32,
+ loc_slice[0..4],
+ target_endian,
+ ),
},
};
}
@@ -1356,9 +1387,10 @@ fn create(
.input_archive_symbol_indices = .empty,
.pending_input = null,
.inputs = .empty,
- .input_resolved = .empty,
+ .input_symbols = .empty,
.input_sections = .empty,
.input_section_pending_index = 0,
+ .inputs_complete = false,
.strings = .empty,
.string_bytes = .empty,
.section_table = .empty,
@@ -1420,7 +1452,7 @@ pub fn deinit(coff: *Coff) void {
coff.input_archive_symbols.deinit(gpa);
coff.input_archive_symbol_indices.deinit(gpa);
coff.inputs.deinit(gpa);
- coff.input_resolved.deinit(gpa);
+ coff.input_symbols.deinit(gpa);
coff.input_sections.deinit(gpa);
coff.strings.deinit(gpa);
coff.string_bytes.deinit(gpa);
@@ -2238,13 +2270,6 @@ fn initSymbolAssumeCapacity(coff: *Coff) !Symbol.Index {
return si;
}
-fn initInputSectionSymbol(coff: *Coff, sym: *Symbol, section_si: Symbol.Index, value: u32) void {
- const section_sym = section_si.get(coff);
- sym.ni = section_sym.ni;
- sym.value = .{ .input_offset = value };
- sym.section_number = section_sym.section_number;
-}
-
fn getOrPutString(coff: *Coff, string: []const u8) !String {
try coff.ensureUnusedStringCapacity(string.len);
return coff.getOrPutStringAssumeCapacity(string);
@@ -2315,7 +2340,7 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
}
const GlobalOptions = struct {
- name: []const u8,
+ name: []const u8, // TODO: Union with String
lib_name: ?[]const u8 = null,
};
@@ -2986,7 +3011,8 @@ fn objectSectionMapIndex(
attributes: ObjectSectionAttributes,
) !Node.ObjectSectionMapIndex {
const gpa = coff.base.comp.gpa;
- const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name.toSlice(coff), ".tls")) attr: {
+ const name_slice = name.toSlice(coff);
+ const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name_slice, ".tls")) attr: {
// In images, the .tls section is a read-only template
var attr = attributes;
attr.write = false;
@@ -2996,8 +3022,7 @@ fn objectSectionMapIndex(
const object_section_gop = try coff.object_section_table.getOrPut(gpa, name);
const osmi: Node.ObjectSectionMapIndex = @enumFromInt(object_section_gop.index);
const sn = if (!object_section_gop.found_existing) sn: {
- try coff.ensureUnusedStringCapacity(name.toSlice(coff).len);
- const name_slice = name.toSlice(coff);
+ try coff.ensureUnusedStringCapacity(name_slice.len);
const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice));
const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff);
try coff.nodes.ensureUnusedCapacity(gpa, 1);
@@ -3047,6 +3072,7 @@ fn objectSectionMapIndex(
return osmi;
}
+// TODO: Include align in attrs and verify the current align is >= requested
fn verifyParentSectionAttributes(
coff: *Coff,
kind: enum { pseudo, object },
@@ -3096,7 +3122,8 @@ pub fn addReloc(
const gpa = coff.base.comp.gpa;
const target = target_si.get(coff);
- log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d}{s})", .{
+ const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len);
+ log.debug("addReloc({d}@{d}+{d} -> {d}@{d}+{d}{s}) = {d}", .{
loc_si,
loc_si.get(coff).section_number,
offset,
@@ -3104,6 +3131,7 @@ pub fn addReloc(
target_si.get(coff).section_number,
if (addend == .pending) 0 else addend.known,
if (addend == .pending) "p" else "k",
+ ri,
});
try coff.relocs.ensureUnusedCapacity(gpa, 1);
@@ -3166,7 +3194,6 @@ pub fn addReloc(
},
};
- const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len);
coff.relocs.addOneAssumeCapacity().* = .{
.type = @"type",
.prev = .none,
@@ -3319,8 +3346,7 @@ fn loadObject(
input.* = .{
.path = path,
.member_name = if (member_name) |m| try gpa.dupe(u8, m) else null,
- .first_si = @enumFromInt(coff.symbols.items.len),
- .end_si = @enumFromInt(coff.symbols.items.len),
+ .source_name = .none,
};
const string_table = string_table: {
@@ -3336,30 +3362,60 @@ fn loadObject(
string_table_len - @sizeOf(u32),
);
- const InputSection = struct {
+ const PendingSymbolIndex = enum(u32) {
+ none,
+ _,
+
+ pub fn wrap(i: ?u32) @This() {
+ return @enumFromInt((i orelse return .none) + 1);
+ }
+
+ pub fn unwrap(i: @This()) ?u32 {
+ return switch (i) {
+ .none => null,
+ _ => @intFromEnum(i) - 1,
+ };
+ }
+ };
+
+ const PendingInputSection = struct {
header: std.coff.SectionHeader,
name: String,
si: Symbol.Index,
+ parent_si: Symbol.Index,
+ psi: PendingSymbolIndex,
+ num_symbols: u32,
+ comdat: std.coff.ComdatSelection,
+ comdat_psi: PendingSymbolIndex,
+ comdat_crc: u32,
+ comdat_association: Symbol.SectionNumber,
+ comdat_result: union(enum) {
+ pending,
+ // Root of the association chain
+ pending_association: Symbol.SectionNumber,
+ include,
+ skip,
+ },
};
- const sections: []const InputSection = if (coff.isImage()) sections: {
- const sections = try gpa.alloc(InputSection, header.number_of_sections);
+ const sections: []PendingInputSection = if (coff.isImage()) sections: {
+ const sections = try gpa.alloc(PendingInputSection, header.number_of_sections);
errdefer gpa.free(sections);
- var num_input_sections: u16 = 0;
- var reqd_object_sections: std.AutoArrayHashMapUnmanaged(String, void) = .empty;
- defer reqd_object_sections.deinit(gpa);
- var reqd_pseudo_sections: std.StringArrayHashMapUnmanaged(void) = .empty;
- defer reqd_pseudo_sections.deinit(gpa);
- try reqd_object_sections.ensureUnusedCapacity(gpa, sections.len);
- try reqd_pseudo_sections.ensureUnusedCapacity(gpa, sections.len);
-
try fr.seekTo(fl.offset + @sizeOf(std.coff.Header));
for (sections, 0..) |*section, section_i| {
section.* = .{
.header = try r.takeStruct(std.coff.SectionHeader, target_endian),
.name = undefined,
.si = .null,
+ .parent_si = .null,
+ .psi = .none,
+ .num_symbols = 0,
+ .comdat = .NONE,
+ .comdat_psi = .none,
+ .comdat_crc = 0,
+ .comdat_association = .UNDEFINED,
+ .comdat_result = .pending,
};
const section_name_slice = if (section.header.name[0] == '/') name: {
@@ -3371,7 +3427,11 @@ fn loadObject(
});
if (name_offset > string_table.len)
- return diags.failParse(path, "out-of-bounds section name offset in section {d}: {d}", .{ section_i, name_offset });
+ return diags.failParse(
+ path,
+ "out-of-bounds section name offset in section {d}: {d}",
+ .{ section_i, name_offset },
+ );
break :name std.mem.sliceTo(string_table[name_offset..], 0);
} else std.mem.sliceTo(§ion.header.name, 0);
@@ -3403,109 +3463,6 @@ fn loadObject(
// TODO: Convert .debug$* sections into PDB
continue;
}
-
- num_input_sections += 1;
- _ = reqd_object_sections.getOrPutAssumeCapacity(section.name);
- _ = reqd_pseudo_sections.getOrPutAssumeCapacity(
- coff.objectSectionParentName(section.name.toSlice(coff)),
- );
- }
-
- var symbol_capacity: u16 = num_input_sections;
- var node_capacity: u16 = 0;
- {
- var iter = reqd_object_sections.count();
- while (iter > 0) {
- iter -= 1;
- if (coff.object_section_table.contains(reqd_object_sections.keys()[iter]))
- reqd_object_sections.swapRemoveAt(iter);
- }
-
- node_capacity += @intCast(reqd_object_sections.count());
- symbol_capacity += @intCast(reqd_object_sections.count());
- }
-
- {
- var iter = reqd_pseudo_sections.count();
- while (iter > 0) {
- // TODO: Track the extra number of strings and their length and reserve? These have not been reserved as
- // part of the ensureManyUnusedStringCapacity call above
- iter -= 1;
- const name = coff.getString(reqd_pseudo_sections.keys()[iter]) orelse continue;
- if (coff.pseudo_section_table.contains(name))
- reqd_pseudo_sections.swapRemoveAt(iter);
- }
-
- node_capacity += @intCast(reqd_pseudo_sections.count());
- symbol_capacity += @intCast(reqd_pseudo_sections.count());
- }
-
- try coff.nodes.ensureUnusedCapacity(gpa, node_capacity);
- try coff.symbols.ensureUnusedCapacity(gpa, symbol_capacity + num_input_sections);
- try coff.input_sections.ensureUnusedCapacity(gpa, num_input_sections);
-
- for (sections) |*section| {
- if (section.header.flags.LNK_INFO) {
- if (std.mem.eql(u8, §ion.header.name, ".drectve")) {
- try fr.seekTo(fl.offset + section.header.pointer_to_raw_data);
- var buf: [128]u8 = undefined;
- var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf);
- while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) {
- error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}),
- else => |e| return e,
- }) |arg| {
- // Microsoft tools emit 3 space characters into this section even with /Zl
- if (arg.len > 0)
- return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg});
- }
- }
-
- continue;
- }
-
- if (section.header.flags.LNK_REMOVE or
- section.header.flags.MEM_DISCARDABLE)
- {
- continue;
- }
-
- if (section.header.flags.LNK_COMDAT)
- // This will be necessary if we do the equivalent of /Gy for compiler-rt
- return diags.failParse(path, "TODO handle COMDAT sections in input objects", .{});
-
- const parent_osmi = try coff.objectSectionMapIndex(
- section.name,
- coff.mf.flags.block_size,
- .fromFlags(section.header.flags),
- );
- const parent_si = parent_osmi.symbol(coff);
- const ni = try coff.mf.addLastChildNode(gpa, parent_si.node(coff), .{
- .size = section.header.size_of_raw_data,
- .alignment = if (section.header.flags.ALIGN.toByteUnits()) |align_bytes|
- .fromByteUnits(align_bytes)
- else
- .@"1",
- .moved = true,
- });
- coff.nodes.appendAssumeCapacity(.{ .input_section = @enumFromInt(coff.input_sections.items.len) });
-
- section.si = coff.addSymbolAssumeCapacity();
- const sym = section.si.get(coff);
- sym.ni = ni;
- sym.section_number = parent_si.get(coff).section_number;
-
- coff.input_sections.addOneAssumeCapacity().* = .{
- .ii = ii,
- .si = section.si,
- .file_location = .{
- .offset = fl.offset + section.header.pointer_to_raw_data,
- .size = section.header.size_of_raw_data,
- },
- .first_iri = @enumFromInt(coff.input_resolved.items.len),
- };
-
- log.debug("loadInputSection({s}) = {d}@{d}", .{ section.name.toSlice(coff), section.si, sym.section_number });
- coff.synth_prog_node.increaseEstimatedTotalItems(1);
}
break :sections sections;
@@ -3522,9 +3479,8 @@ fn loadObject(
const member = mi.get(coff);
try member.initHeader(coff, path_str, header.time_date_stamp);
- // TODO: This could be deferred to an idle task?
-
{
+ // TODO: This could be deferred to an idle task?
var nw: MappedFile.Node.Writer = undefined;
member.content_ni.writer(&coff.mf, gpa, &nw);
defer nw.deinit();
@@ -3537,18 +3493,35 @@ fn loadObject(
break :mi mi;
} else undefined;
- // TODO: Also reserve memory for the symbols / globals / relocs within each section
-
try fr.seekTo(fl.offset + header.pointer_to_symbol_table);
- const symbol_size = comptime std.coff.Symbol.sizeOf();
+ const symbol_size = std.coff.Symbol.sizeOf();
- var symbols: std.ArrayList(Symbol.Index) = .empty;
- try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
- var num_resolved: u32 = 0;
+ const PendingSymbol = struct {
+ name: String,
+ value: union(enum) {
+ // Size of the section
+ section: u32,
+ // Offset within the section
+ static: u32,
+ // If section is defined, the symbol size. Otherwise offset within the section.
+ external: u32,
+ // The index of the target symbol of this alias
+ weak_external: u32,
+ },
+ section_number: Symbol.SectionNumber,
+ si: Symbol.Index,
+ // The index of the weak_external that targest this symbol
+ weak_external_psi: PendingSymbolIndex,
+ };
+
+ var num_global_symbols: u32 = 0;
+ var pending_symbols: std.AutoArrayHashMapUnmanaged(u32, PendingSymbol) = .empty;
+ defer pending_symbols.deinit(gpa);
- input.first_si = @enumFromInt(coff.symbols.items.len);
- defer input.end_si = @enumFromInt(coff.symbols.items.len);
+ if (!is_archive)
+ try pending_symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
+ // Discover symbol names and COMDAT symbol mappings
var symbol_i: u32 = 0;
while (symbol_i < header.number_of_symbols) {
var symbol: std.coff.Symbol = undefined;
@@ -3556,10 +3529,11 @@ fn loadObject(
if (target_endian != native_endian)
std.mem.byteSwapAllFields(std.coff.Symbol, &symbol);
- defer {
- r.toss(symbol.number_of_aux_symbols * symbol_size);
- symbol_i += symbol.number_of_aux_symbols + 1;
- }
+ const aux_symbols = if (symbol.number_of_aux_symbols > 0)
+ try r.take(symbol_size * symbol.number_of_aux_symbols)
+ else
+ &.{};
+ defer symbol_i += symbol.number_of_aux_symbols + 1;
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);
@@ -3568,145 +3542,499 @@ fn loadObject(
break :name string_table[index..];
} else &symbol.name, 0);
- const si_slice = symbols.addManyAsSliceAssumeCapacity(1 + symbol.number_of_aux_symbols);
- @memset(si_slice, .null);
-
- defer log.debug("loadInputSymbol({s}, 0x{x}) = {d}@{d}", .{
- name,
- symbol.value,
- si_slice[0],
- if (si_slice[0] == .null) .UNDEFINED else si_slice[0].get(coff).section_number,
- });
-
if (is_archive) {
- if (symbol.storage_class == .EXTERNAL and symbol.section_number != .UNDEFINED)
- try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name));
+ if (switch (symbol.storage_class) {
+ .WEAK_EXTERNAL => true,
+ .EXTERNAL => symbol.section_number != .UNDEFINED,
+ else => false,
+ }) try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name));
continue;
}
- switch (symbol.storage_class) {
- .STATIC, .LABEL => |storage_class| switch (symbol.section_number) {
- .UNDEFINED, .DEBUG, .ABSOLUTE => {
- // TODO: Do we need to do anything with @feat.00?
- // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392
- },
+ switch (symbol.section_number) {
+ .UNDEFINED, .DEBUG, .ABSOLUTE => {},
+ else => |sn| if (@intFromEnum(sn) > sections.len)
+ return diags.failParse(path, "out-of-bounds section number {d} in symbol 0x{x}", .{ sn, symbol_i }),
+ }
+
+ const psi: PendingSymbolIndex = .wrap(@intCast(pending_symbols.count()));
+ const section_number: Symbol.SectionNumber = @enumFromInt(@intFromEnum(symbol.section_number));
+ const opt_value: ?@FieldType(PendingSymbol, "value") = pending_symbol: switch (symbol.storage_class) {
+ .STATIC, .LABEL => |storage_class| switch (section_number) {
+ // TODO: Do we need to do anything with @feat.00?
+ // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392
+ .UNDEFINED, .DEBUG, .ABSOLUTE => null,
else => |sn| {
+ const section = §ions[sn.toIndex()];
+
// Section symbol
- if (storage_class == .STATIC and
+ const is_section = storage_class == .STATIC and
symbol.value == 0 and
symbol.type == std.coff.SymType{
.complex_type = .NULL,
.base_type = .NULL,
} and
- symbol.number_of_aux_symbols > 0)
- {
+ symbol.number_of_aux_symbols > 0;
+
+ if (is_section) {
if (symbol.number_of_aux_symbols > 1)
- return diags.failParse(path, "invalid number of aux symbols for section 0x{x}: {d}", .{
+ return diags.failParse(path, "invalid number of aux symbols for section symbol 0x{x}: {d}", .{
symbol_i,
symbol.number_of_aux_symbols,
});
var section_def: std.coff.SectionDefinition = undefined;
- @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], try r.peek(symbol_size));
+ @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], aux_symbols[0..symbol_size]);
if (target_endian != native_endian)
std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def);
- // TODO: Extract the COMDAT section info
-
- if (section_def.number > sections.len)
- return diags.failParse(
- path,
- "section symbol for '{s}' contained an out of bounds section number: 0x{x}",
- .{ name, section_def.number },
- );
-
- // It's valid for this to not match the symbol's section number (ie. .drectve sets this)
- if (section_def.number == 0)
- continue;
-
- const section = §ions[section_def.number - 1];
if (section_def.number_of_relocations != section.header.number_of_relocations)
return diags.failParse(
path,
- "section symbol for '{s}' relocation count did not match section header: {d} vs {d}",
- .{ name, section_def.number_of_relocations, section.header.number_of_relocations },
+ "section aux symbol 0x{x} for '{s}' relocation count did not match section header: {d} vs {d}",
+ .{ symbol_i + 1, name, section_def.number_of_relocations, section.header.number_of_relocations },
);
if (section_def.number_of_linenumbers != section.header.number_of_linenumbers)
return diags.failParse(
path,
- "section symbol for '{s}' line number count did not match section header: {d} vs {d}",
- .{ name, section_def.number_of_linenumbers, section.header.number_of_linenumbers },
+ "section aux symbol 0x{x} for '{s}' line number count did not match section header: {d} vs {d}",
+ .{ symbol_i + 1, name, section_def.number_of_linenumbers, section.header.number_of_linenumbers },
);
- @memset(si_slice, section.si);
- } else {
- try coff.symbols.ensureUnusedCapacity(gpa, 1);
- si_slice[0] = coff.addSymbolAssumeCapacity();
- const sym = si_slice[0].get(coff);
- coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value);
+ if (section.header.flags.LNK_COMDAT) {
+ if (section_def.selection == .ASSOCIATIVE) {
+ if (section_def.number == 0 or section_def.number > sections.len)
+ return diags.failParse(
+ path,
+ "section aux symbol 0x{x} for '{s}' contained an invalid associated section number: 0x{x}",
+ .{ symbol_i + 1, name, section_def.number },
+ );
+
+ section.comdat_association = @enumFromInt(section_def.number);
+ }
+
+ section.comdat = section_def.selection;
+ section.comdat_crc = section_def.checksum;
+ }
+
+ section.psi = psi;
}
+
+ break :pending_symbol if (is_section)
+ .{ .section = section.header.size_of_raw_data }
+ else
+ .{ .static = symbol.value };
},
},
- .EXTERNAL => switch (symbol.section_number) {
+ .WEAK_EXTERNAL => switch (symbol.section_number) {
.UNDEFINED => {
- const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name });
- si_slice[0] = global_gop.value_ptr.*;
- if (!global_gop.found_existing) {
- const sym = si_slice[0].get(coff);
- sym.value = .{ .size = symbol.value };
- }
+ if (symbol.value != 0)
+ return diags.failParse(
+ path,
+ "invalid value {d} for weak external symbol 0x{x}",
+ .{ symbol.value, symbol_i },
+ );
+
+ var weak_external: std.coff.WeakExternalDefinition = undefined;
+ @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]);
+ if (target_endian != native_endian)
+ std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external);
+
+ if (weak_external.tag_index >= header.number_of_symbols)
+ return diags.failParse(
+ path,
+ "invalid tag_index 0x{x} for weak external symbol 0x{x}",
+ .{ weak_external.tag_index, symbol_i },
+ );
+
+ break :pending_symbol switch (weak_external.flag) {
+ .SEARCH_NOLIBRARY,
+ .SEARCH_LIBRARY,
+ => return diags.failParse(
+ path,
+ "TODO handle weak external characteristic 0x{x} for symbol 0x{x}",
+ .{ weak_external.flag, symbol_i },
+ ),
+ .SEARCH_ALIAS => .{ .weak_external = weak_external.tag_index },
+ else => return diags.failParse(
+ path,
+ "encountered unknown weak external characteristic 0x{x} for symbol 0x{x}",
+ .{ weak_external.flag, symbol_i },
+ ),
+ };
},
+ else => |sn| return diags.failParse(
+ path,
+ "invalid section number {d} for weak external symbol 0x{x}",
+ .{ sn, symbol_i },
+ ),
+ },
+ .EXTERNAL => switch (section_number) {
+ .UNDEFINED => .{ .external = symbol.value },
.ABSOLUTE => return diags.failParse(
path,
- "TODO unhandled external absolute symbol: '{s}'",
- .{name},
+ "TODO unhandled external absolute symbol 0x{x}: '{s}'",
+ .{ symbol_i, name },
),
.DEBUG => return diags.failParse(
path,
- "unexpected external symbol in DEBUG section: '{s}'",
- .{name},
+ "unexpected external symbol 0x{x} in DEBUG section: '{s}'",
+ .{ symbol_i, name },
),
- else => |sn| {
- // TODO: Should this use archive name as lib_name as well?
- const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = name });
- si_slice[0] = global_gop.value_ptr.*;
- const sym = si_slice[0].get(coff);
- if (global_gop.found_existing and sym.ni != .none) {
- // TODO: Need corresponding logic later if we try to make a global already defined by an input
- var err = try diags.addErrorWithNotes(2);
- try err.addMsg("multiple definitions of '{s}'", .{name});
- switch (coff.getNode(sym.ni)) {
- .input_section => |isi| {
- const other_ii = isi.input(coff);
- err.addNote("first seen in input '{f}{f}'", .{
- other_ii.path(coff).fmtEscapeString(),
- fmtMemberNameString(other_ii.memberName(coff)),
- });
- },
- .nav, .uav => err.addNote("first seen in module '{s}'", .{
- comp.zcu.?.root_mod.fully_qualified_name,
- }),
- else => unreachable,
- }
- err.addNote("defined again in input '{f}'", .{path});
- return error.LinkFailure;
+ else => .{ .external = symbol.value },
+ },
+ .FILE => {
+ if (!std.mem.eql(u8, name, ".file"))
+ return diags.failParse(
+ path,
+ "unexpected symbol name '{s}' for file symbol 0x{x}",
+ .{ name, symbol_i },
+ );
+
+ var file: std.coff.FileDefinition = undefined;
+ @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]);
+
+ input.source_name = (try coff.getOrPutString(file.getFileName())).toOptional();
+ break :pending_symbol null;
+ },
+ else => |storage_class| return diags.failParse(
+ path,
+ "TODO handle storage class {t} for symbol 0x{x}",
+ .{ storage_class, symbol_i },
+ ),
+ };
+
+ if (opt_value) |value| {
+ switch (value) {
+ .section => {},
+ .static, .external, .weak_external => {
+ num_global_symbols += 1;
+ if (section_number.hasIndex()) {
+ const section = §ions[section_number.toIndex()];
+ section.num_symbols += 1;
+ if (section.header.flags.LNK_COMDAT and section.comdat_psi == .none)
+ section.comdat_psi = psi;
}
+ },
+ }
+
+ const symbol_name = coff.getOrPutStringAssumeCapacity(name);
+ pending_symbols.putAssumeCapacity(symbol_i, .{
+ .name = symbol_name,
+ .value = value,
+ .section_number = section_number,
+ .si = .null,
+ .weak_external_psi = .none,
+ });
+ }
+ }
+
+ try coff.globals.ensureUnusedCapacity(gpa, num_global_symbols);
+ for (sections) |*section| {
+ if (section.header.flags.LNK_INFO) {
+ if (std.mem.eql(u8, §ion.header.name, ".drectve")) {
+ try fr.seekTo(fl.offset + section.header.pointer_to_raw_data);
+ var buf: [128]u8 = undefined;
+ var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf);
+ while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) {
+ error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}),
+ else => |e| return e,
+ }) |arg| {
+ // Microsoft tools emit 3 space characters into this section even with /Zl
+ if (arg.len > 0)
+ return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg});
+ }
+ }
+
+ section.comdat_result = .skip;
+ continue;
+ }
+
+ if (section.header.flags.LNK_REMOVE or
+ section.header.flags.MEM_DISCARDABLE)
+ {
+ section.comdat_result = .skip;
+ continue;
+ }
+
+ section.comdat_result = comdat: switch (section.comdat) {
+ .NONE => .include,
+ .ASSOCIATIVE => {
+ // Associative COMDAT sections have no COMDAT symbol.
+ // They are linked if the assocated section is linked.
+ var iter = section;
+ var iter_sn = iter.comdat_association;
+ while (iter.comdat == .ASSOCIATIVE) {
+ iter = §ions[iter_sn.toIndex()];
+ iter_sn = iter.comdat_association;
+ if (iter == section)
+ return diags.failParse(
+ path,
+ "circular COMDAT association loop detected, starting at symbol 0x{x}",
+ .{pending_symbols.keys()[section.psi.unwrap().?]},
+ );
+ }
+
+ assert(iter != section);
+ break :comdat switch (iter.comdat_result) {
+ .pending => .{ .pending_association = iter_sn },
+ else => |iter_result| iter_result,
+ };
+ },
+ else => |comdat| {
+ const psi = section.comdat_psi.unwrap() orelse
+ return diags.failParse(
+ path,
+ "COMDAT section symbol 0x{x} had no COMDAT symbol",
+ .{pending_symbols.keys()[section.psi.unwrap().?]},
+ );
- if (global_gop.found_existing)
- num_resolved += 1;
+ const symbol = &pending_symbols.values()[psi];
+ switch (symbol.value) {
+ .section, .weak_external => unreachable,
+ .static => break :comdat .include,
+ else => {},
+ }
+
+ // TODO: Do we need to use lib_name here?
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff), .lib_name = null });
+ if (!global_gop.found_existing) {
+ symbol.si = global_gop.value_ptr.*;
+ break :comdat .include;
+ }
+
+ const index = pending_symbols.keys()[psi];
+ const si = global_gop.value_ptr.*;
+ switch (comdat) {
+ .NODUPLICATES => return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ si,
+ .duplicate,
+ ),
+ .ANY => break :comdat .skip,
+ .SAME_SIZE => {
+ // TODO: Verify that this node isn't resized after creation
+ _, const size = si.get(coff).ni.location(&coff.mf).resolve(&coff.mf);
+ if (size == section.header.size_of_raw_data)
+ break :comdat .skip;
+
+ return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ si,
+ .{ .size = .{ .a = size, .b = section.header.size_of_raw_data } },
+ );
+ },
+ .EXACT_MATCH => {
+ const sym = si.get(coff);
+ const existing_crc = switch (coff.getNode(sym.ni)) {
+ .input_section => |isi| isi.inputSection(coff).crc,
+ // TODO: Should this result be cached somewhere?
+ else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.slice(&coff.mf)),
+ };
+
+ if (existing_crc == section.comdat_crc)
+ break :comdat .skip;
+
+ return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ si,
+ .{ .crc = .{ .a = existing_crc, .b = section.comdat_crc } },
+ );
+ },
+ .LARGEST => {
+ // TODO: Resize existing .ni and replace with this section's contents
+ // TODO: This will be tricky, what to do about existing InputSection?
+ unreachable; // TODO
+ },
+ .NONE, .ASSOCIATIVE, _ => unreachable,
+ }
+ },
+ };
+ }
+
+ // Resolve pending associations, create parent sections
+ var num_included_sections: u16 = 0;
+ var num_included_symbols: u32 = 0;
+ var num_included_relocs: u32 = 0;
+ for (sections) |*section| {
+ comdat: switch (section.comdat_result) {
+ .pending_association => |root_assoc_sn| {
+ const root_result = sections[root_assoc_sn.toIndex()].comdat_result;
+ assert(root_result != .pending_association);
+ section.comdat_result = root_result;
+ continue :comdat root_result;
+ },
+ .include => {},
+ .skip => continue,
+ .pending => unreachable,
+ }
+
+ num_included_sections += 1;
+ num_included_symbols += section.num_symbols;
+ num_included_relocs += section.header.number_of_relocations;
+
+ section.parent_si = (try coff.objectSectionMapIndex(
+ section.name,
+ section.header.flags.ALIGN.alignment() orelse .@"1",
+ .fromFlags(section.header.flags),
+ )).symbol(coff);
+ }
+
+ try coff.nodes.ensureUnusedCapacity(gpa, num_included_sections);
+ try coff.relocs.ensureUnusedCapacity(gpa, num_included_relocs);
+ try coff.symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
+ try coff.input_sections.ensureUnusedCapacity(gpa, num_included_sections);
+
+ for (sections) |*section| {
+ if (section.comdat_result != .include) continue;
+
+ const ni = try coff.mf.addLastChildNode(gpa, section.parent_si.node(coff), .{
+ .size = section.header.size_of_raw_data,
+ .alignment = section.header.flags.ALIGN.alignment() orelse .@"1",
+ .moved = true,
+ });
+ coff.nodes.appendAssumeCapacity(.{ .input_section = @enumFromInt(coff.input_sections.items.len) });
+
+ section.si = coff.addSymbolAssumeCapacity();
+ if (section.psi.unwrap()) |psi|
+ pending_symbols.values()[psi].si = section.si;
+
+ const sym = section.si.get(coff);
+ sym.ni = ni;
+ sym.section_number = section.parent_si.get(coff).section_number;
+
+ coff.input_sections.addOneAssumeCapacity().* = .{
+ .ii = ii,
+ .si = section.si,
+ .file_location = .{
+ .offset = fl.offset + section.header.pointer_to_raw_data,
+ .size = section.header.size_of_raw_data,
+ },
+ .first_li = @enumFromInt(coff.input_symbols.items.len),
+ .crc = section.comdat_crc,
+ };
+
+ log.debug(
+ "addInputSection({s}, 0x{x}) = {d}@{d}",
+ .{ section.name.toSlice(coff), section.comdat_crc, section.si, sym.section_number },
+ );
+ coff.synth_prog_node.increaseEstimatedTotalItems(1);
+ }
+
+ for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, psi| {
+ const section = switch (symbol.section_number) {
+ .UNDEFINED => switch (symbol.value) {
+ .section,
+ .static,
+ => unreachable,
+ .external,
+ .weak_external,
+ => |value, tag| {
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
+ symbol.si = global_gop.value_ptr.*;
+ if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
+ const sym = symbol.si.get(coff);
+ if (tag == .external) {
+ // TOOD: Is it valid to encounter multiple external definitions with different sizes?
+ assert(sym.value == .size);
+ sym.value = .{ .size = @max(sym.value.size, value) };
+ } else {
+ const alias = pending_symbols.getPtr(value) orelse
+ return diags.failParse(
+ path,
+ "weak external 0x{x} {s}{f} targets unknown symbol index 0x{x}",
+ .{
+ index,
+ symbol.name.toSlice(coff),
+ fmtMemberNameString(member_name),
+ value,
+ },
+ );
+
+ if (alias.si == .null) {
+ alias.weak_external_psi = .wrap(@intCast(psi));
+ } else {
+ sym.value = .{ .alias_si = alias.si };
+ }
+ }
+ }
- coff.initInputSectionSymbol(sym, sections[@intCast(@intFromEnum(sn) - 1)].si, symbol.value);
+ continue;
},
},
- else => {},
+ .ABSOLUTE, .DEBUG => continue,
+ else => |sn| §ions[sn.toIndex()],
+ };
+
+ if (section.si == .null)
+ continue;
+
+ if (symbol.si == .null) {
+ switch (symbol.value) {
+ .section => unreachable,
+ .static => {
+ symbol.si = coff.addSymbolAssumeCapacity();
+ },
+ .external => {
+ // COMDAT symbols were created when enumerating the sections
+ assert(section.comdat == .NONE);
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
+ symbol.si = global_gop.value_ptr.*;
+
+ const sym = symbol.si.get(coff);
+ if (global_gop.found_existing and sym.ni != .none)
+ return coff.failMultipleDefinitions(path, member_name, symbol.name, index, global_gop.value_ptr.*, .none);
+ },
+ .weak_external => unreachable,
+ }
+ }
+
+ if (symbol.weak_external_psi.unwrap()) |i| {
+ assert(symbol.si != .null);
+ pending_symbols.values()[i].si.get(coff).value = .{ .alias_si = symbol.si };
+ }
+
+ if (section.si != symbol.si) {
+ const sym = symbol.si.get(coff);
+ sym.ni = section.si.get(coff).ni;
+ sym.value = switch (symbol.value) {
+ .section => |v| .{ .size = v },
+ .static => |v| .{ .input_offset = v },
+ .external => |v| switch (symbol.section_number) {
+ .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable,
+ else => .{ .input_offset = v },
+ },
+ .weak_external => unreachable,
+ };
+ sym.section_number = symbol.section_number;
}
+
+ defer log.debug("addInputSymbol({s}, 0x{x}, {t}=0x{x}) = {d}@{d}", .{
+ symbol.name.toSlice(coff),
+ index,
+ symbol.value,
+ switch (symbol.value) {
+ inline else => |v| v,
+ },
+ symbol.si,
+ section.si.get(coff).section_number,
+ });
}
const relocation_size = std.coff.Relocation.sizeOf();
for (sections) |section| {
- if (section.si == .null) continue;
+ if (section.comdat_result != .include) continue;
const loc_sym = section.si.get(coff);
assert(loc_sym.loc_relocs == .none);
@@ -3714,7 +4042,6 @@ fn loadObject(
if (section.header.number_of_relocations == 0) continue;
- try coff.relocs.ensureUnusedCapacity(gpa, section.header.number_of_relocations);
try fr.seekTo(fl.offset + section.header.pointer_to_relocations);
for (0..section.header.number_of_relocations) |reloc_i| {
var reloc: std.coff.Relocation = undefined;
@@ -3722,57 +4049,110 @@ fn loadObject(
if (target_endian != native_endian)
std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
- if (reloc.symbol_table_index >= symbols.items.len)
+ // TODO: This error should show member name for lib
+ const symbol = pending_symbols.get(reloc.symbol_table_index) orelse
return diags.failParse(
path,
- "relocation 0x{x} in section '{s}' targets invalid symbol index 0x{x}",
- .{ reloc_i, section.name.toSlice(coff), reloc.symbol_table_index },
+ "relocation 0x{x} in section '{s}'{f} targets invalid symbol index 0x{x}",
+ .{ reloc_i, section.name.toSlice(coff), fmtMemberNameString(member_name), reloc.symbol_table_index },
);
- assert(symbols.items[reloc.symbol_table_index] != .null);
+ assert(symbol.si != .null);
try coff.addReloc(
section.si,
reloc.virtual_address - section.header.virtual_address,
- symbols.items[reloc.symbol_table_index],
+ symbol.si,
.pending,
@bitCast(reloc.type),
);
}
}
- const symbolLessThan = struct {
- fn lessThan(ctx: *Coff, lhs: Symbol.Index, rhs: Symbol.Index) bool {
- const lhs_sn = @intFromEnum(if (lhs == .null) .UNDEFINED else lhs.get(ctx).section_number);
- const rhs_sn = @intFromEnum(if (rhs == .null) .UNDEFINED else rhs.get(ctx).section_number);
- if (lhs_sn == rhs_sn) return @intFromEnum(lhs) < @intFromEnum(rhs);
- return lhs_sn < rhs_sn;
+ // Set up contiguous symbol ranges in `input_symbols` for both symbols we just created,
+ // and symbols that were previously created as undefined, but we just defined.
+ const SortContext = struct {
+ v: []const PendingSymbol,
+
+ pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
+ const lhs = &ctx.v[a_index];
+ const rhs = &ctx.v[b_index];
+ if (lhs.section_number == rhs.section_number)
+ return @intFromEnum(lhs.si) < @intFromEnum(rhs.si);
+ return @intFromEnum(lhs.section_number) < @intFromEnum(rhs.section_number);
}
- }.lessThan;
+ };
+
+ pending_symbols.sortUnstable(SortContext{ .v = pending_symbols.values() });
+
+ try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
+ var prev_sn: Symbol.SectionNumber = .UNDEFINED;
+ for (pending_symbols.values()) |symbol| {
+ // The symbol may have not been included, or it's an undefined external
+ if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue;
+ assert(coff.getNode(symbol.si.get(coff).ni) == .input_section);
- std.mem.sortUnstable(Symbol.Index, symbols.items, coff, symbolLessThan);
-
- // Any symbols that we resolved (used to be undefined but are now defined) in this pass need to be
- // added to contigous ranges in `input_resolved` so they can be visited in `flushMoved`, as they
- // are not part of the contiguous ii.first_si / ii.last_si range.
- //
- // TODO: Should we just use this array for all symbols in this input? More memory but less get().ni misses in flushMoved
- try coff.input_resolved.ensureUnusedCapacity(gpa, num_resolved);
- var prev_isi: ?Node.InputSection.Index = null;
- for (symbols.items) |si| {
- if (si == .null or @intFromEnum(si) >= @intFromEnum(input.end_si)) continue;
- const ni = si.get(coff).ni;
- if (ni == .none) continue;
-
- const isi = coff.getNode(ni).input_section;
- if (prev_isi != isi) {
- isi.inputSection(coff).first_iri = @enumFromInt(coff.input_resolved.items.len);
- prev_isi = isi;
+ if (prev_sn != symbol.section_number) {
+ prev_sn = symbol.section_number;
+
+ const section = §ions[symbol.section_number.toIndex()];
+ const isi = coff.getNode(section.si.get(coff).ni).input_section;
+ isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len);
}
- coff.input_resolved.addOneAssumeCapacity().* = si;
+ coff.input_symbols.addOneAssumeCapacity().* = symbol.si;
}
}
+fn failMultipleDefinitions(
+ coff: *Coff,
+ path: std.Build.Cache.Path,
+ member_name: ?[]const u8,
+ name: String,
+ index: u32,
+ existing_si: Symbol.Index,
+ comdat_reason: union(enum) {
+ none: void,
+ duplicate: void,
+ size: struct { a: u64, b: u64 },
+ crc: struct { a: u32, b: u32 },
+ },
+) error{ LinkFailure, OutOfMemory } {
+ const num_notes: usize = 2 + @as(usize, @intFromBool(comdat_reason != .none));
+ var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes);
+ try err.addMsg("multiple definitions of '{s}'", .{name.toSlice(coff)});
+
+ switch (coff.getNode(existing_si.get(coff).ni)) {
+ .input_section => |isi| {
+ const other_ii = isi.input(coff);
+ err.addNote("first seen in input '{f}{f}'", .{
+ other_ii.path(coff).fmtEscapeString(),
+ fmtMemberNameString(other_ii.memberName(coff)),
+ });
+ },
+ .nav, .uav => err.addNote("first seen in module '{s}'", .{
+ coff.base.comp.zcu.?.root_mod.fully_qualified_name,
+ }),
+ //else => |_, tag| err.addNote("TODO multiple def for {t}", .{tag}),
+ else => unreachable,
+ }
+
+ err.addNote("defined again in input '{f}{f}' (0x{x}))", .{ path, fmtMemberNameString(member_name), index });
+ switch (comdat_reason) {
+ .none => {},
+ .duplicate => err.addNote("COMDAT rule requires no duplicates", .{}),
+ .size => |s| err.addNote(
+ "COMDAT rule require duplicates to have the same size ({d} vs {d})",
+ .{ s.a, s.b },
+ ),
+ .crc => |s| err.addNote(
+ "COMDAT rule require duplicates to have the same CRC (0x{x} vs 0x{x})",
+ .{ s.a, s.b },
+ ),
+ }
+
+ return error.LinkFailure;
+}
+
const ArchiveMemberHeader = struct {
name: []const u8,
size: u34,
@@ -4078,10 +4458,10 @@ fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void {
}
pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
- _ = coff;
_ = prog_node;
-
log.debug("prelink()", .{});
+
+ coff.inputs_complete = true;
}
pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
@@ -4410,17 +4790,16 @@ fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
num_unique_references = 1;
}
- const num_notes =
- @min(max_notes, num_unique_references) +
- @intFromBool(num_unique_references > max_notes);
-
- var err = try comp.link_diags.addErrorWithNotes(num_notes);
+ const num_full_notes = @min(max_notes, num_unique_references);
+ var err = try comp.link_diags.addErrorWithNotes(
+ num_full_notes + @intFromBool(num_unique_references > max_notes),
+ );
const target_sym = target.get(coff);
try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.globalName(coff).name.toSlice(coff)});
var prev_loc_si: Symbol.Index = .null;
for (undef_indices.items[start_i..][0..@max(1, i - start_i)]) |reference_i| {
- if (err.note_slot == num_notes) break;
+ if (err.note_slot == num_full_notes) break;
const loc_si = coff.relocs.items[reference_i].loc;
if (loc_si == prev_loc_si) continue;
@@ -4537,18 +4916,27 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
break :task;
}
if (coff.pending_input) |pending_iami| {
- // TODO: Prog node?
+ const name_slice = pending_iami.member(coff).name.toSlice(coff);
+ const sub_prog_node = coff.input_prog_node.start(
+ name_slice,
+ 0,
+ );
+ defer sub_prog_node.end();
coff.pending_input = null;
coff.flushInputMember(pending_iami) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => |e| return comp.link_diags.fail(
- "linker failed to load archive member: {t}",
- .{e},
+ "linker failed to load archive member '{f}{f}': {t}",
+ .{
+ pending_iami.member(coff).iai.path(coff),
+ fmtMemberNameString(name_slice),
+ e,
+ },
),
};
break :task;
}
- if (coff.global_pending_index < coff.globals.count()) {
+ if (coff.inputs_complete and coff.global_pending_index < coff.globals.count()) {
const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index);
const sub_prog_node = coff.synth_prog_node.start(
gmi.globalName(coff).name.toSlice(coff),
@@ -4699,7 +5087,7 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
}
if (coff.pending_uavs.count() > 0) return true;
if (coff.pending_input != null) return true;
- if (coff.globals.count() > coff.global_pending_index) return true;
+ if (coff.inputs_complete and coff.globals.count() > coff.global_pending_index) return true;
for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
if (coff.symbol_table.pending.count() > 0) return true;
if (coff.input_sections.items.len > coff.input_section_pending_index) return true;
@@ -4810,12 +5198,17 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
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) });
+ const si = gmi.symbol(coff);
+ const sym = si.get(coff);
+
+ log.debug(
+ "flushGlobal({s}, {?s}) = {d} ({d})",
+ .{ gn.name.toSlice(coff), gn.lib_name.toSlice(coff), si, sym.ni },
+ );
if (!coff.isImage()) {
- const si = gmi.symbol(coff);
try coff.pendingSymbolTableEntry(si);
- if (coff.isArchive() and si.get(coff).ni != .none)
+ if (coff.isArchive() and sym.ni != .none)
try coff.ensureMemberSymbol(
coff.getNode(Node.known.zcu_member).archive_member,
gn.name,
@@ -4945,8 +5338,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas;
},
}
- const si = gmi.symbol(coff);
- const sym = si.get(coff);
sym.section_number = Symbol.Index.text.get(coff).section_number;
assert(sym.loc_relocs == .none);
sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
@@ -4980,14 +5371,49 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
coff.nodes.appendAssumeCapacity(.{ .global = gmi });
sym.rva = coff.computeNodeRva(sym.ni);
si.applyLocationRelocs(coff);
- } else {
- if (coff.input_archive_symbol_indices.get(gn.name)) |index| {
+ } else if (sym.ni == .none) {
+ switch (sym.value) {
+ .alias_si => |alias_si| {
+ assert(sym.section_number == .UNDEFINED);
+ assert(sym.loc_relocs == .none);
+
+ const alias_sym = alias_si.get(coff);
+ var ri = sym.target_relocs;
+ while (ri != .none) {
+ const reloc = ri.get(coff);
+ assert(reloc.target == si);
+ reloc.target = alias_si;
+ if (reloc.next == .none) {
+ reloc.next = alias_sym.target_relocs;
+ if (alias_sym.target_relocs != .none)
+ alias_sym.target_relocs.get(coff).prev = ri;
+ }
+ ri = reloc.next;
+ }
+
+ sym.target_relocs = .none;
+ coff.globals.values()[gmi.unwrap().?] = alias_si;
+ alias_si.applyTargetRelocs(coff);
+
+ log.debug("flushGlobal({s}, {?s}) alias {d}->{d}", .{
+ gmi.globalName(coff).name.toSlice(coff),
+ gmi.globalName(coff).lib_name.toSlice(coff),
+ si,
+ alias_si,
+ });
+
+ return true;
+ },
+ .size => {},
+ .input_offset => unreachable,
+ }
+
+ if (coff.input_archive_symbol_indices.get(gmi.globalName(coff).name)) |index| {
var iter: InputArchive.Member.Symbol.Index = index.first;
while (true) {
const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
-
- // TODO: This implies that loading an input failing is not fatal
if (!coff.input_archive_members.items[@intFromEnum(archive_sym.iami)].flags.is_loaded) {
+ // Try loading the input member and then retry
coff.pending_input = archive_sym.iami;
return false;
}
@@ -4996,8 +5422,6 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
iter = archive_sym.next;
}
}
-
- // TODO: Check if we can get flushGlobal before prelink, that would cause a problem
}
return true;
@@ -5116,19 +5540,8 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
);
},
.input_section => |isi| {
- const ii = isi.input(coff);
isi.symbol(coff).flushMoved(coff);
-
- {
- var si = ii.firstSymbol(coff);
- const end_si = ii.endSymbol(coff);
- while (@intFromEnum(si) < @intFromEnum(end_si)) : (si = si.next()) {
- if (si.get(coff).ni != ni) continue;
- si.flushMoved(coff);
- }
- }
-
- for (coff.input_resolved.items[@intFromEnum(isi.firstResolvedSymbol(coff))..]) |si| {
+ for (coff.input_symbols.items[@intFromEnum(isi.firstSymbol(coff))..]) |si| {
if (si.get(coff).ni != ni) break;
si.flushMoved(coff);
}
@@ -5444,7 +5857,7 @@ fn flushExportsSort(coff: *Coff) void {
entries: []ExportTable.Entry,
nt: []const u8,
- pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
+ pub fn lessThan(ctx: *const @This(), lhs: usize, rhs: usize) bool {
const lhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[lhs].unbiased_ordinal)];
const rhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[rhs].unbiased_ordinal)];
return std.mem.lessThan(
@@ -5460,7 +5873,7 @@ fn flushExportsSort(coff: *Coff) void {
}
};
- std.sort.pdqContext(0, coff.export_table.entries.count(), Context{
+ std.sort.pdqContext(0, coff.export_table.entries.count(), &Context{
.coff = coff,
.np = coff.exportNamePointerTableSlice(),
.ord = coff.exportOrdinalTableSlice(),