authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-09-19 13:25:56-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-09-19 13:25:56-04:00
log96c654941c98017e9e59fe38e06d9a334fb6c80c
tree20ba7df157bc8a6618a85f62e84c4dd8e1523152
parent5a4fda7e36e9862d6c0fd8fe15d7bd5cefa2f6e9

Coff: rework global creation to avoid invalidating strings that were themselves used as arguments


1 files changed, 156 insertions(+), 119 deletions(-)

src/link/Coff.zig+156-119
......@@ -278,7 +278,7 @@ pub const Node = union(enum) {
278278 return coff.globals.values()[gmi.unwrap().?].si;
279279 }
280280
281 pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) String.Optional {
281 pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) ExternLibName {
282282 return coff.globals.values()[gmi.unwrap().?].lib_name;
283283 }
284284 };
......@@ -864,7 +864,7 @@ pub const Section = struct {
864864
865865pub const Global = struct {
866866 si: Symbol.Index,
867 lib_name: String.Optional,
867 lib_name: ExternLibName,
868868};
869869
870870pub const WeakExternalStrat = enum(u3) {
......@@ -2781,37 +2781,43 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
27812781 return @fromBackingInt(@intCast(gop.key_ptr.*));
27822782}
27832783
2784const GlobalOptions = struct {
2785 name: []const u8,
2786 lib_name: ?[]const u8 = null,
2787 type: Symbol.Type = .unknown,
2788 dll_storage_class: Symbol.DllStorageClass = .default,
2784const ExternLibName = union(enum) {
2785 // Symbol can be supplied from anywhere
2786 any,
2787 // Symbol can be supplied by any LibC input
2788 lib_c,
2789 // Symbol must be supplied by a specific input
2790 explicit: String,
27892791};
27902792
2793const FmtExternLibName = struct { coff: *Coff, lib_name: ExternLibName };
2794
2795fn fmtExternLibName(coff: *Coff, lib_name: ExternLibName) std.fmt.Alt(FmtExternLibName, formatExternLibName) {
2796 return .{ .data = .{ .coff = coff, .lib_name = lib_name } };
2797}
2798
2799fn formatExternLibName(data: FmtExternLibName, w: *Io.Writer) Io.Writer.Error!void {
2800 try w.writeAll(switch (data.lib_name) {
2801 .any => "*",
2802 .lib_c => "c",
2803 .explicit => |str| str.toSlice(data.coff),
2804 });
2805}
2806
27912807fn getOrPutGlobalSymbol(
27922808 coff: *Coff,
2793 opts: GlobalOptions,
2809 opts: struct {
2810 name: String,
2811 lib_name: ExternLibName = .any,
2812 type: Symbol.Type = .unknown,
2813 dll_storage_class: Symbol.DllStorageClass = .default,
2814 },
27942815) !std.array_hash_map.Auto(String, Global).GetOrPutResult {
27952816 const comp = coff.base.comp;
27962817 const gpa = comp.gpa;
27972818 try coff.symbols.ensureUnusedCapacity(gpa, 1);
27982819
2799 const lib_name: String.Optional = if (opts.lib_name) |lib_name| lib_name: {
2800 const is_libc = std.zig.target.isLibCLibName(&comp.root_mod.resolved_target.result, lib_name);
2801 if (is_libc) {
2802 // This is guaranteed by Sema.handleExternLibName
2803 if (!comp.config.link_libc) unreachable;
2804
2805 // TODO: The user has requested this symbol come from libc, but this logic allows
2806 // it to come from anywhere. We need to know what inputs are libc inputs,
2807 // and set a flag to only search them for this symbol.
2808 break :lib_name .none;
2809 }
2810
2811 break :lib_name (try coff.getOrPutString(lib_name)).toOptional();
2812 } else .none;
2813
2814 const sym_gop = try coff.globals.getOrPut(gpa, try coff.getOrPutString(opts.name));
2820 const sym_gop = try coff.globals.getOrPut(gpa, opts.name);
28152821 if (!sym_gop.found_existing) {
28162822 const si = coff.addSymbolAssumeCapacity();
28172823 const sym = si.get(coff);
......@@ -2820,11 +2826,11 @@ fn getOrPutGlobalSymbol(
28202826 sym.flags.dll_storage_class = opts.dll_storage_class;
28212827 sym_gop.value_ptr.* = .{
28222828 .si = si,
2823 .lib_name = lib_name,
2829 .lib_name = opts.lib_name,
28242830 };
28252831 coff.synth_prog_node.increaseEstimatedTotalItems(1);
28262832
2827 log.debug("globalSymbol({s}, {?s}) = {d}", .{ opts.name, opts.lib_name, si });
2833 log.debug("globalSymbol({s}, {f}) = {d}", .{ opts.name.toSlice(coff), fmtExternLibName(coff, opts.lib_name), si });
28282834 }
28292835
28302836 return sym_gop;
......@@ -2837,8 +2843,36 @@ fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index {
28372843 return .null;
28382844}
28392845
2840pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
2841 const gop = try coff.getOrPutGlobalSymbol(opts);
2846pub fn globalSymbol(coff: *Coff, opts: struct {
2847 name: []const u8,
2848 lib_name: ?[]const u8 = null,
2849 type: Symbol.Type = .unknown,
2850 dll_storage_class: Symbol.DllStorageClass = .default,
2851}) !Symbol.Index {
2852 const comp = coff.base.comp;
2853 const lib_name: ExternLibName = if (opts.lib_name) |lib_name| lib_name: {
2854 const is_libc = std.zig.target.isLibCLibName(
2855 &comp.root_mod.resolved_target.result,
2856 lib_name,
2857 );
2858
2859 if (is_libc) {
2860 // This is guaranteed by Sema.handleExternLibName
2861 if (!comp.config.link_libc) unreachable;
2862 break :lib_name .lib_c;
2863 }
2864
2865 try coff.ensureManyUnusedStringCapacity(2, opts.name.len + lib_name.len);
2866 break :lib_name .{ .explicit = coff.getOrPutStringAssumeCapacity(lib_name) };
2867 } else .any;
2868
2869 try coff.ensureUnusedStringCapacity(opts.name.len);
2870 const gop = try coff.getOrPutGlobalSymbol(.{
2871 .name = coff.getOrPutStringAssumeCapacity(opts.name),
2872 .lib_name = lib_name,
2873 .type = opts.type,
2874 .dll_storage_class = opts.dll_storage_class,
2875 });
28422876 return gop.value_ptr.si;
28432877}
28442878
......@@ -3547,13 +3581,16 @@ fn objectSectionMapIndex(
35473581 attributes: ObjectSectionAttributes,
35483582) !Node.ObjectSectionMapIndex {
35493583 const gpa = coff.base.comp.gpa;
3550 const name_slice = name.toSlice(coff);
35513584
35523585 const object_section_gop = try coff.object_section_table.getOrPut(gpa, name);
35533586 const osmi: Node.ObjectSectionMapIndex = @fromBackingInt(@intCast(object_section_gop.index));
35543587 const sym = if (!object_section_gop.found_existing) sym: {
3555 try coff.ensureUnusedStringCapacity(name_slice.len);
3556 const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice));
3588 const name_slice = name.toSlice(coff);
3589 const parent_name_slice = coff.objectSectionParentName(name_slice);
3590 const parent_name = if (parent_name_slice.len < name_slice.len) parent_name: {
3591 try coff.ensureUnusedStringCapacity(parent_name_slice.len);
3592 break :parent_name coff.getOrPutStringAssumeCapacity(name.toSlice(coff)[0..parent_name_slice.len]);
3593 } else name;
35573594 const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, attributes)).symbol(coff);
35583595 try coff.nodes.ensureUnusedCapacity(gpa, 1);
35593596 try coff.symbols.ensureUnusedCapacity(gpa, 1);
......@@ -4540,7 +4577,7 @@ fn loadObject(
45404577 },
45414578 .external => {
45424579 const global_gop = try coff.getOrPutGlobalSymbol(.{
4543 .name = symbol.name.toSlice(coff),
4580 .name = symbol.name,
45444581 });
45454582
45464583 // TODO: What if the same symbol is incorrectly defined twice in this obj?
......@@ -4747,7 +4784,7 @@ fn loadObject(
47474784 continue;
47484785 },
47494786 .weak_external => |alias_index| {
4750 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4787 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name });
47514788 symbol.si = global_gop.value_ptr.si;
47524789 if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
47534790 const sym = symbol.si.get(coff);
......@@ -4786,7 +4823,7 @@ fn loadObject(
47864823 break :sym value;
47874824 },
47884825 .external => |value| {
4789 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4826 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name });
47904827 symbol.si = global_gop.value_ptr.si;
47914828 if (global_gop.found_existing)
47924829 return coff.failMultipleDefinitions(
......@@ -4822,7 +4859,7 @@ fn loadObject(
48224859 },
48234860 .external => {
48244861 assert(index != section.comdat_psi.unwrap());
4825 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4862 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name });
48264863 symbol.si = global_gop.value_ptr.si;
48274864
48284865 const sym = symbol.si.get(coff);
......@@ -4906,7 +4943,7 @@ fn loadObject(
49064943 assert(symbol.section_number == .UNDEFINED);
49074944 switch (symbol.value) {
49084945 .external => |size| {
4909 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4946 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name });
49104947 symbol.si = global_gop.value_ptr.si;
49114948 if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
49124949 const sym = symbol.si.get(coff);
......@@ -6244,9 +6281,9 @@ fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !v
62446281 assert(sym.section_number == .UNDEFINED);
62456282 assert(sym.loc_relocs == .none);
62466283
6247 log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{
6284 log.debug("aliasGlobal({s}, {f}) {d}->{d} ({?s})", .{
62486285 gmi.name(coff).toSlice(coff),
6249 gmi.libName(coff).toSlice(coff),
6286 fmtExternLibName(coff, gmi.libName(coff)),
62506287 si,
62516288 alias_si,
62526289 if (alias_sym.gmi != .none) alias_sym.gmi.name(coff).toSlice(coff) else null,
......@@ -6284,10 +6321,10 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
62846321 const si = gmi.symbol(coff);
62856322
62866323 log.debug(
6287 "flushGlobal({s}, {?s}) = n{d} {d}@{d}",
6324 "flushGlobal({s}, {f}) = n{d} {d}@{d}",
62886325 .{
62896326 name.toSlice(coff),
6290 gmi.libName(coff).toSlice(coff),
6327 fmtExternLibName(coff, gmi.libName(coff)),
62916328 si.get(coff).ni,
62926329 si,
62936330 si.get(coff).section_number,
......@@ -6362,68 +6399,72 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
63626399 while (true) {
63636400 const archive_sym = &coff.input_archive_symbols.items[@backingInt(iter)];
63646401 const member = &coff.input_archive_members.items[@backingInt(archive_sym.iami)];
6365 member: switch (member.content) {
6366 .object => if (!member.flags.is_loaded) {
6367 if (gmi.libName(coff).unwrap()) |lib_name|
6368 if (!std.ascii.eqlIgnoreCase(
6369 lib_name.toSlice(coff),
6370 member.iai.path(coff).stem(),
6371 )) break :member;
6372
6373 // Try loading the input member and then retry.
6374 // This could still be a member containing imports
6375 // that use the older non-IMPORT_HEADER method.
6376 coff.pending_input = archive_sym.iami;
6377 return false;
6378 },
6379 .import => |import| {
6380 if (gmi.libName(coff).unwrap()) |lib_name|
6381 if (!std.ascii.eqlIgnoreCase(
6382 import.lib_name.toSlice(coff),
6383 lib_name.toSlice(coff),
6384 )) break :member;
6385
6386 const imp_name: String.Optional = name: switch (import.name_type) {
6387 .NAME,
6388 .NAME_NOPREFIX,
6389 .NAME_UNDECORATE,
6390 => |tag| {
6391 const symbol_name: []const u8 = import.symbol_name.toSlice(coff);
6392 const end_match = std.mem.endsWith(u8, name_slice, symbol_name);
6393 const len_delta = name_slice.len -% symbol_name.len;
6394 if (!end_match or
6395 (!imp_match and len_delta != 0) or
6396 (imp_match and len_delta != imp_prefix.len))
6397 return comp.link_diags.fail(
6398 "global '{s}' has mismatched symbol name in import header: '{s}'",
6399 .{
6400 name.toSlice(coff),
6401 import.symbol_name.toSlice(coff),
6402 },
6403 );
6402 const lib_name_matches = match: switch (gmi.libName(coff)) {
6403 .any => true,
6404 .lib_c => {
6405 // TODO: Need a flag to indicate if this member is from a libc archive
6406 const is_libc_archive = true;
64046407
6405 const imp_name = if (tag == .NAME) import.symbol_name else undecorated: {
6406 var imp_name = std.mem.trimStart(u8, symbol_name, "?@_");
6407 if (tag == .NAME_UNDECORATE)
6408 imp_name = std.mem.sliceTo(imp_name, '@');
6409
6410 try coff.ensureUnusedStringCapacity(imp_name.len);
6411 break :undecorated coff.getOrPutStringAssumeCapacity(imp_name);
6412 };
6408 break :match is_libc_archive;
6409 },
6410 .explicit => |lib_name| std.ascii.eqlIgnoreCase(
6411 lib_name.toSlice(coff),
6412 member.iai.path(coff).stem(),
6413 ),
6414 };
64136415
6414 break :name imp_name.toOptional();
6415 },
6416 .ORDINAL => break :name .none,
6417 else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}),
6418 };
6416 if (lib_name_matches) {
6417 switch (member.content) {
6418 .object => if (!member.flags.is_loaded) {
6419 // Try loading the input member and then retry.
6420 // This could still be a member containing imports
6421 // that use the older non-IMPORT_HEADER method.
6422 coff.pending_input = archive_sym.iami;
6423 return false;
6424 },
6425 .import => |import| {
6426 const imp_name: String.Optional = name: switch (import.name_type) {
6427 .NAME,
6428 .NAME_NOPREFIX,
6429 .NAME_UNDECORATE,
6430 => |tag| {
6431 const symbol_name: []const u8 = import.symbol_name.toSlice(coff);
6432 const end_match = std.mem.endsWith(u8, name_slice, symbol_name);
6433 const len_delta = name_slice.len -% symbol_name.len;
6434 if (!end_match or
6435 (!imp_match and len_delta != 0) or
6436 (imp_match and len_delta != imp_prefix.len))
6437 return comp.link_diags.fail(
6438 "global '{s}' has mismatched symbol name in import header: '{s}'",
6439 .{
6440 name.toSlice(coff),
6441 import.symbol_name.toSlice(coff),
6442 },
6443 );
6444
6445 const imp_name = if (tag == .NAME) import.symbol_name else undecorated: {
6446 var imp_name = std.mem.trimStart(u8, symbol_name, "?@_");
6447 if (tag == .NAME_UNDECORATE)
6448 imp_name = std.mem.sliceTo(imp_name, '@');
6449
6450 try coff.ensureUnusedStringCapacity(imp_name.len);
6451 break :undecorated coff.getOrPutStringAssumeCapacity(imp_name);
6452 };
6453
6454 break :name imp_name.toOptional();
6455 },
6456 .ORDINAL => break :name .none,
6457 else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}),
6458 };
64196459
6420 break :import .{
6421 .lib_name = import.lib_name,
6422 .name = imp_name,
6423 .ordinal_hint = import.import_ordinal_hint,
6424 .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr,
6425 };
6426 },
6460 break :import .{
6461 .lib_name = import.lib_name,
6462 .name = imp_name,
6463 .ordinal_hint = import.import_ordinal_hint,
6464 .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr,
6465 };
6466 },
6467 }
64276468 }
64286469
64296470 if (archive_sym.next == iter) break;
......@@ -6440,7 +6481,7 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
64406481 // Convert an unresolved weak external that itself refers to an undef external
64416482 // into a (possibly new) global, so it can be resolved separately.
64426483 const alias_gop = try coff.getOrPutGlobalSymbol(.{
6443 .name = sym.value.weak_alias_name.toSlice(coff),
6484 .name = sym.value.weak_alias_name,
64446485 });
64456486 try coff.aliasGlobal(gmi, alias_gop.value_ptr.si);
64466487 return true;
......@@ -6460,12 +6501,15 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
64606501 // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification,
64616502 // which are not in the implib.
64626503 if (sym.flags.type != .unknown) {
6463 if (gmi.libName(coff).unwrap()) |lib_name| break :import .{
6464 .lib_name = lib_name,
6465 .name = name.toOptional(),
6466 .ordinal_hint = 0,
6467 .kind = if (sym.flags.type == .code) .thunk else .iat_ptr,
6468 };
6504 switch (gmi.libName(coff)) {
6505 .explicit => |lib_name| break :import .{
6506 .lib_name = lib_name,
6507 .name = name.toOptional(),
6508 .ordinal_hint = 0,
6509 .kind = if (sym.flags.type == .code) .thunk else .iat_ptr,
6510 },
6511 else => {},
6512 }
64696513 }
64706514
64716515 return true;
......@@ -7660,8 +7704,10 @@ fn fmtGlobalName(coff: *Coff, gmi: Node.GlobalMapIndex) std.fmt.Alt(FmtGlobalNam
76607704fn globalNameEscape(data: FmtGlobalName, w: *std.Io.Writer) std.Io.Writer.Error!void {
76617705 if (data.gmi == .none) return;
76627706 try w.writeAll(data.gmi.name(data.coff).toSlice(data.coff));
7663 if (data.gmi.libName(data.coff).unwrap()) |lib_name|
7664 try w.print("({s})", .{lib_name.toSlice(data.coff)});
7707 switch (data.gmi.libName(data.coff)) {
7708 .explicit => |lib_name| try w.print(" ({s}.dll)", .{lib_name.toSlice(data.coff)}),
7709 else => {},
7710 }
76657711}
76667712
76677713fn printNodeName(
......@@ -7703,12 +7749,7 @@ fn printNodeName(
77037749 inline .pseudo_section, .object_section => |smi| try w.print("({s})", .{
77047750 smi.name(coff).toSlice(coff),
77057751 }),
7706 .import_thunk,
7707 => |gmi| {
7708 try w.writeByte('(');
7709 if (gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
7710 try w.print("{s})", .{gmi.name(coff).toSlice(coff)});
7711 },
7752 .import_thunk => |gmi| try w.print("({f})", .{fmtGlobalName(coff, gmi)}),
77127753 .nav => |nmi| {
77137754 const zcu = coff.base.comp.zcu.?;
77147755 const ip = &zcu.intern_pool;
......@@ -7734,11 +7775,7 @@ fn printNodeName(
77347775 }),
77357776 .builtin => |si| {
77367777 const sym = si.get(coff);
7737 if (sym.gmi != .none) {
7738 try w.writeByte('(');
7739 if (sym.gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
7740 try w.print("{s})", .{sym.gmi.name(coff).toSlice(coff)});
7741 }
7778 if (sym.gmi != .none) try w.print("({f})", .{fmtGlobalName(coff, sym.gmi)});
77427779 },
77437780 }
77447781}