Skip to content

Commit b7fd803

Browse files
committed
compiler: push entry symbol name resolution into the linker
This is necessary because on COFF, the entry symbol name is not known until the linker has looked at the set of global symbol names to determine which of the four possible main entry points is present.
1 parent e5041ea commit b7fd803

File tree

11 files changed

+124
-99
lines changed

11 files changed

+124
-99
lines changed

src/Compilation.zig

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ pub const LinkObject = struct {
933933
loption: bool = false,
934934
};
935935

936-
pub const InitOptions = struct {
936+
pub const CreateOptions = struct {
937937
zig_lib_directory: Directory,
938938
local_cache_directory: Directory,
939939
global_cache_directory: Directory,
@@ -1063,7 +1063,7 @@ pub const InitOptions = struct {
10631063
/// infinite recursion.
10641064
skip_linker_dependencies: bool = false,
10651065
hash_style: link.File.Elf.HashStyle = .both,
1066-
entry: ?[]const u8 = null,
1066+
entry: Entry = .default,
10671067
force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{},
10681068
stack_size: ?u64 = null,
10691069
image_base: ?u64 = null,
@@ -1098,6 +1098,8 @@ pub const InitOptions = struct {
10981098
/// (Windows) PDB output path
10991099
pdb_out_path: ?[]const u8 = null,
11001100
error_limit: ?Compilation.Module.ErrorInt = null,
1101+
1102+
pub const Entry = link.File.OpenOptions.Entry;
11011103
};
11021104

11031105
fn addModuleTableToCacheHash(
@@ -1168,7 +1170,7 @@ fn addModuleTableToCacheHash(
11681170
}
11691171
}
11701172

1171-
pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1173+
pub fn create(gpa: Allocator, options: CreateOptions) !*Compilation {
11721174
const output_mode = options.config.output_mode;
11731175
const is_dyn_lib = switch (output_mode) {
11741176
.Obj, .Exe => false,
@@ -1552,6 +1554,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
15521554
.dynamicbase = options.linker_dynamicbase,
15531555
.major_subsystem_version = options.major_subsystem_version,
15541556
.minor_subsystem_version = options.minor_subsystem_version,
1557+
.entry = options.entry,
15551558
.stack_size = options.stack_size,
15561559
.image_base = options.image_base,
15571560
.version_script = options.version_script,

src/Compilation/Config.zig

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export_memory: bool,
5555
shared_memory: bool,
5656
is_test: bool,
5757
test_evented_io: bool,
58-
entry: ?[]const u8,
5958
debug_format: DebugFormat,
6059
root_strip: bool,
6160
root_error_tracing: bool,
@@ -100,12 +99,6 @@ pub const Options = struct {
10099
use_lld: ?bool = null,
101100
use_clang: ?bool = null,
102101
lto: ?bool = null,
103-
entry: union(enum) {
104-
default,
105-
disabled,
106-
enabled,
107-
named: []const u8,
108-
} = .default,
109102
/// WASI-only. Type of WASI execution model ("command" or "reactor").
110103
wasi_exec_model: ?std.builtin.WasiExecModel = null,
111104
import_memory: ?bool = null,
@@ -123,8 +116,6 @@ pub const ResolveError = error{
123116
ObjectFilesCannotShareMemory,
124117
SharedMemoryRequiresAtomicsAndBulkMemory,
125118
ThreadsRequireSharedMemory,
126-
UnknownTargetEntryPoint,
127-
NonExecutableEntryPoint,
128119
EmittingLlvmModuleRequiresLlvmBackend,
129120
LlvmLacksTargetSupport,
130121
ZigLacksTargetSupport,
@@ -352,25 +343,6 @@ pub fn resolve(options: Options) ResolveError!Config {
352343
break :b false;
353344
};
354345

355-
const entry: ?[]const u8 = switch (options.entry) {
356-
.disabled => null,
357-
.default => b: {
358-
if (options.output_mode != .Exe) break :b null;
359-
360-
// When producing C source code, the decision of entry point is made
361-
// when compiling the C code, not when producing the C code.
362-
if (target.ofmt == .c) break :b null;
363-
364-
break :b target_util.defaultEntrySymbolName(target, wasi_exec_model) orelse
365-
return error.UnknownTargetEntryPoint;
366-
},
367-
.enabled => target_util.defaultEntrySymbolName(target, wasi_exec_model) orelse
368-
return error.UnknownTargetEntryPoint,
369-
.named => |name| name,
370-
};
371-
if (entry != null and options.output_mode != .Exe)
372-
return error.NonExecutableEntryPoint;
373-
374346
const any_unwind_tables = options.any_unwind_tables or
375347
link_libunwind or target_util.needUnwindTables(target);
376348

@@ -519,7 +491,6 @@ pub fn resolve(options: Options) ResolveError!Config {
519491
.use_llvm = use_llvm,
520492
.use_lib_llvm = use_lib_llvm,
521493
.use_lld = use_lld,
522-
.entry = entry,
523494
.wasi_exec_model = wasi_exec_model,
524495
.debug_format = debug_format,
525496
.root_strip = root_strip,

src/link.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ pub const File = struct {
8383
symbol_count_hint: u64 = 32,
8484
program_code_size_hint: u64 = 256 * 1024,
8585

86+
/// This may depend on what symbols are found during the linking process.
87+
entry: Entry,
8688
/// Virtual address of the entry point procedure relative to image base.
8789
entry_addr: ?u64,
8890
stack_size: ?u64,
@@ -169,6 +171,13 @@ pub const File = struct {
169171
module_definition_file: ?[]const u8,
170172

171173
wasi_emulated_libs: []const wasi_libc.CRTFile,
174+
175+
pub const Entry = union(enum) {
176+
default,
177+
disabled,
178+
enabled,
179+
named: []const u8,
180+
};
172181
};
173182

174183
/// Attempts incremental linking, if the file already exists. If

src/link/Coff.zig

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dynamicbase: bool,
1717
major_subsystem_version: u16,
1818
minor_subsystem_version: u16,
1919
lib_dirs: []const []const u8,
20+
entry: link.File.OpenOptions.Entry,
2021
entry_addr: ?u32,
2122
module_definition_file: ?[]const u8,
2223
pdb_out_path: ?[]const u8,
@@ -303,7 +304,12 @@ pub fn createEmpty(
303304
.Obj => 0,
304305
},
305306

307+
// Subsystem depends on the set of public symbol names from linked objects.
308+
// See LinkerDriver::inferSubsystem from the LLD project for the flow chart.
306309
.subsystem = options.subsystem,
310+
311+
.entry = options.entry,
312+
307313
.tsaware = options.tsaware,
308314
.nxcompat = options.nxcompat,
309315
.dynamicbase = options.dynamicbase,
@@ -2498,7 +2504,20 @@ inline fn getSizeOfImage(self: Coff) u32 {
24982504
/// Returns symbol location corresponding to the set entrypoint (if any).
24992505
pub fn getEntryPoint(self: Coff) ?SymbolWithLoc {
25002506
const comp = self.base.comp;
2501-
const entry_name = comp.config.entry orelse return null;
2507+
2508+
// TODO This is incomplete.
2509+
// The entry symbol name depends on the subsystem as well as the set of
2510+
// public symbol names from linked objects.
2511+
// See LinkerDriver::findDefaultEntry from the LLD project for the flow chart.
2512+
const entry_name = switch (self.entry) {
2513+
.disabled => return null,
2514+
.default => switch (comp.config.output_mode) {
2515+
.Exe => "wWinMainCRTStartup",
2516+
.Obj, .Lib => return null,
2517+
},
2518+
.enabled => "wWinMainCRTStartup",
2519+
.named => |name| name,
2520+
};
25022521
const global_index = self.resolver.get(entry_name) orelse return null;
25032522
return self.globals.items[global_index];
25042523
}

src/link/Coff/lld.zig

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
5252
const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib;
5353
const target = comp.root_mod.resolved_target.result;
5454
const optimize_mode = comp.root_mod.optimize_mode;
55+
const entry_name: ?[]const u8 = switch (self.entry) {
56+
// This logic isn't quite right for disabled or enabled. No point in fixing it
57+
// when the goal is to eliminate dependency on LLD anyway.
58+
// https://github.com/ziglang/zig/issues/17751
59+
.disabled, .default, .enabled => null,
60+
.named => |name| name,
61+
};
5562

5663
// See link/Elf.zig for comments on how this mechanism works.
5764
const id_symlink_basename = "lld.id";
@@ -80,7 +87,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
8087
}
8188
}
8289
try man.addOptionalFile(module_obj_path);
83-
man.hash.addOptionalBytes(comp.config.entry);
90+
man.hash.addOptionalBytes(entry_name);
8491
man.hash.add(self.base.stack_size);
8592
man.hash.add(self.image_base);
8693
man.hash.addListOfBytes(self.lib_dirs);
@@ -218,8 +225,8 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
218225
try argv.append("-DLL");
219226
}
220227

221-
if (comp.config.entry) |entry| {
222-
try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry}));
228+
if (entry_name) |name| {
229+
try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{name}));
223230
}
224231

225232
if (self.tsaware) {
@@ -441,7 +448,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
441448
}
442449
} else {
443450
try argv.append("-NODEFAULTLIB");
444-
if (!is_lib and comp.config.entry == null) {
451+
if (!is_lib and entry_name == null) {
445452
if (comp.module) |module| {
446453
if (module.stage1_flags.have_winmain_crt_startup) {
447454
try argv.append("-ENTRY:WinMainCRTStartup");

src/link/Elf.zig

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ linker_script: ?[]const u8,
2525
version_script: ?[]const u8,
2626
print_icf_sections: bool,
2727
print_map: bool,
28+
entry_name: ?[]const u8,
2829

2930
ptr_width: PtrWidth,
3031

@@ -290,6 +291,13 @@ pub fn createEmpty(
290291
.page_size = page_size,
291292
.default_sym_version = default_sym_version,
292293

294+
.entry_name = switch (options.entry) {
295+
.disabled => null,
296+
.default => if (output_mode != .Exe) null else defaultEntrySymbolName(target.cpu.arch),
297+
.enabled => defaultEntrySymbolName(target.cpu.arch),
298+
.named => |name| name,
299+
},
300+
293301
.image_base = b: {
294302
if (is_dyn_lib) break :b 0;
295303
if (output_mode == .Exe and comp.config.pie) break :b 0;
@@ -1305,7 +1313,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
13051313

13061314
// Look for entry address in objects if not set by the incremental compiler.
13071315
if (self.entry_index == null) {
1308-
if (comp.config.entry) |name| {
1316+
if (self.entry_name) |name| {
13091317
self.entry_index = self.globalByName(name);
13101318
}
13111319
}
@@ -1679,9 +1687,8 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void {
16791687
}
16801688
}
16811689

1682-
if (comp.config.entry) |entry| {
1683-
try argv.append("--entry");
1684-
try argv.append(entry);
1690+
if (self.entry_name) |name| {
1691+
try argv.appendSlice(&.{ "--entry", name });
16851692
}
16861693

16871694
for (self.base.rpath_list) |rpath| {
@@ -2427,7 +2434,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
24272434

24282435
// We can skip hashing libc and libc++ components that we are in charge of building from Zig
24292436
// installation sources because they are always a product of the compiler version + target information.
2430-
man.hash.addOptionalBytes(comp.config.entry);
2437+
man.hash.addOptionalBytes(self.entry_name);
24312438
man.hash.add(self.image_base);
24322439
man.hash.add(self.base.gc_sections);
24332440
man.hash.addOptional(self.sort_section);
@@ -2563,9 +2570,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
25632570
.ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
25642571
}
25652572

2566-
if (comp.config.entry) |entry| {
2567-
try argv.append("--entry");
2568-
try argv.append(entry);
2573+
if (self.entry_name) |name| {
2574+
try argv.appendSlice(&.{ "--entry", name });
25692575
}
25702576

25712577
for (self.base.force_undefined_symbols.keys()) |sym| {
@@ -6512,6 +6518,13 @@ const RelaSectionTable = std.AutoArrayHashMapUnmanaged(u32, RelaSection);
65126518
pub const R_X86_64_ZIG_GOT32 = elf.R_X86_64_NUM + 1;
65136519
pub const R_X86_64_ZIG_GOTPCREL = elf.R_X86_64_NUM + 2;
65146520

6521+
fn defaultEntrySymbolName(cpu_arch: std.Target.Cpu.Arch) []const u8 {
6522+
return switch (cpu_arch) {
6523+
.mips, .mipsel, .mips64, .mips64el => "__start",
6524+
else => "_start",
6525+
};
6526+
}
6527+
65156528
const std = @import("std");
65166529
const build_options = @import("build_options");
65176530
const builtin = @import("builtin");

src/link/MachO.zig

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
base: File,
2+
entry_name: ?[]const u8,
23

34
/// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path.
45
llvm_object: ?*LlvmObject = null,
@@ -231,6 +232,12 @@ pub fn createEmpty(
231232
.install_name = options.install_name,
232233
.entitlements = options.entitlements,
233234
.compatibility_version = options.compatibility_version,
235+
.entry_name = switch (options.entry) {
236+
.disabled => null,
237+
.default => if (output_mode != .Exe) null else default_entry_symbol_name,
238+
.enabled => default_entry_symbol_name,
239+
.named => |name| name,
240+
},
234241
};
235242
if (use_llvm and comp.config.have_zcu) {
236243
self.llvm_object = try LlvmObject.create(arena, comp);
@@ -1629,8 +1636,9 @@ pub fn resolveSymbols(self: *MachO) !void {
16291636
// we search for it in libraries should there be no object files specified
16301637
// on the linker line.
16311638
if (output_mode == .Exe) {
1632-
const entry_name = comp.config.entry.?;
1633-
_ = try self.addUndefined(entry_name, .{});
1639+
if (self.entry_name) |entry_name| {
1640+
_ = try self.addUndefined(entry_name, .{});
1641+
}
16341642
}
16351643

16361644
// Force resolution of any symbols requested by the user.
@@ -5085,8 +5093,7 @@ pub fn getStubsEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
50855093
/// Returns symbol location corresponding to the set entrypoint if any.
50865094
/// Asserts output mode is executable.
50875095
pub fn getEntryPoint(self: MachO) ?SymbolWithLoc {
5088-
const comp = self.base.comp;
5089-
const entry_name = comp.config.entry orelse return null;
5096+
const entry_name = self.entry_name orelse return null;
50905097
const global = self.getGlobal(entry_name) orelse return null;
50915098
return global;
50925099
}
@@ -5645,6 +5652,8 @@ pub fn logAtom(self: *MachO, atom_index: Atom.Index, logger: anytype) void {
56455652
}
56465653
}
56475654

5655+
const default_entry_symbol_name = "_main";
5656+
56485657
pub const base_tag: File.Tag = File.Tag.macho;
56495658
pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1)));
56505659
pub const N_BOUNDARY: u16 = @as(u16, @bitCast(@as(i16, -2)));

src/link/MachO/zld.zig

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,8 @@ pub fn linkWithZld(
276276
try argv.append("-dead_strip_dylibs");
277277
}
278278

279-
if (comp.config.entry) |entry| {
280-
try argv.append("-e");
281-
try argv.append(entry);
279+
if (macho_file.entry_name) |entry_name| {
280+
try argv.appendSlice(&.{ "-e", entry_name });
282281
}
283282

284283
for (objects) |obj| {

0 commit comments

Comments
 (0)