Commit 57562c8d50

Andrew Kelley <andrew@ziglang.org>
2023-12-27 05:39:39
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 c9fe436
src/Compilation/Config.zig
@@ -55,7 +55,6 @@ export_memory: bool,
 shared_memory: bool,
 is_test: bool,
 test_evented_io: bool,
-entry: ?[]const u8,
 debug_format: DebugFormat,
 root_strip: bool,
 root_error_tracing: bool,
@@ -100,12 +99,6 @@ pub const Options = struct {
     use_lld: ?bool = null,
     use_clang: ?bool = null,
     lto: ?bool = null,
-    entry: union(enum) {
-        default,
-        disabled,
-        enabled,
-        named: []const u8,
-    } = .default,
     /// WASI-only. Type of WASI execution model ("command" or "reactor").
     wasi_exec_model: ?std.builtin.WasiExecModel = null,
     import_memory: ?bool = null,
@@ -123,8 +116,6 @@ pub const ResolveError = error{
     ObjectFilesCannotShareMemory,
     SharedMemoryRequiresAtomicsAndBulkMemory,
     ThreadsRequireSharedMemory,
-    UnknownTargetEntryPoint,
-    NonExecutableEntryPoint,
     EmittingLlvmModuleRequiresLlvmBackend,
     LlvmLacksTargetSupport,
     ZigLacksTargetSupport,
@@ -352,25 +343,6 @@ pub fn resolve(options: Options) ResolveError!Config {
         break :b false;
     };
 
-    const entry: ?[]const u8 = switch (options.entry) {
-        .disabled => null,
-        .default => b: {
-            if (options.output_mode != .Exe) break :b null;
-
-            // When producing C source code, the decision of entry point is made
-            // when compiling the C code, not when producing the C code.
-            if (target.ofmt == .c) break :b null;
-
-            break :b target_util.defaultEntrySymbolName(target, wasi_exec_model) orelse
-                return error.UnknownTargetEntryPoint;
-        },
-        .enabled => target_util.defaultEntrySymbolName(target, wasi_exec_model) orelse
-            return error.UnknownTargetEntryPoint,
-        .named => |name| name,
-    };
-    if (entry != null and options.output_mode != .Exe)
-        return error.NonExecutableEntryPoint;
-
     const any_unwind_tables = options.any_unwind_tables or
         link_libunwind or target_util.needUnwindTables(target);
 
@@ -519,7 +491,6 @@ pub fn resolve(options: Options) ResolveError!Config {
         .use_llvm = use_llvm,
         .use_lib_llvm = use_lib_llvm,
         .use_lld = use_lld,
-        .entry = entry,
         .wasi_exec_model = wasi_exec_model,
         .debug_format = debug_format,
         .root_strip = root_strip,
src/link/Coff/lld.zig
@@ -52,6 +52,13 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
     const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib;
     const target = comp.root_mod.resolved_target.result;
     const optimize_mode = comp.root_mod.optimize_mode;
+    const entry_name: ?[]const u8 = switch (self.entry) {
+        // This logic isn't quite right for disabled or enabled. No point in fixing it
+        // when the goal is to eliminate dependency on LLD anyway.
+        // https://github.com/ziglang/zig/issues/17751
+        .disabled, .default, .enabled => null,
+        .named => |name| name,
+    };
 
     // See link/Elf.zig for comments on how this mechanism works.
     const id_symlink_basename = "lld.id";
@@ -80,7 +87,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
             }
         }
         try man.addOptionalFile(module_obj_path);
-        man.hash.addOptionalBytes(comp.config.entry);
+        man.hash.addOptionalBytes(entry_name);
         man.hash.add(self.base.stack_size);
         man.hash.add(self.image_base);
         man.hash.addListOfBytes(self.lib_dirs);
@@ -218,8 +225,8 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
             try argv.append("-DLL");
         }
 
-        if (comp.config.entry) |entry| {
-            try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry}));
+        if (entry_name) |name| {
+            try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{name}));
         }
 
         if (self.tsaware) {
@@ -441,7 +448,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
                     }
                 } else {
                     try argv.append("-NODEFAULTLIB");
-                    if (!is_lib and comp.config.entry == null) {
+                    if (!is_lib and entry_name == null) {
                         if (comp.module) |module| {
                             if (module.stage1_flags.have_winmain_crt_startup) {
                                 try argv.append("-ENTRY:WinMainCRTStartup");
src/link/MachO/zld.zig
@@ -276,9 +276,8 @@ pub fn linkWithZld(
                 try argv.append("-dead_strip_dylibs");
             }
 
-            if (comp.config.entry) |entry| {
-                try argv.append("-e");
-                try argv.append(entry);
+            if (macho_file.entry_name) |entry_name| {
+                try argv.appendSlice(&.{ "-e", entry_name });
             }
 
             for (objects) |obj| {
src/link/Coff.zig
@@ -17,6 +17,7 @@ dynamicbase: bool,
 major_subsystem_version: u16,
 minor_subsystem_version: u16,
 lib_dirs: []const []const u8,
+entry: link.File.OpenOptions.Entry,
 entry_addr: ?u32,
 module_definition_file: ?[]const u8,
 pdb_out_path: ?[]const u8,
@@ -303,7 +304,12 @@ pub fn createEmpty(
             .Obj => 0,
         },
 
+        // Subsystem depends on the set of public symbol names from linked objects.
+        // See LinkerDriver::inferSubsystem from the LLD project for the flow chart.
         .subsystem = options.subsystem,
+
+        .entry = options.entry,
+
         .tsaware = options.tsaware,
         .nxcompat = options.nxcompat,
         .dynamicbase = options.dynamicbase,
@@ -2498,7 +2504,20 @@ inline fn getSizeOfImage(self: Coff) u32 {
 /// Returns symbol location corresponding to the set entrypoint (if any).
 pub fn getEntryPoint(self: Coff) ?SymbolWithLoc {
     const comp = self.base.comp;
-    const entry_name = comp.config.entry orelse return null;
+
+    // TODO This is incomplete.
+    // The entry symbol name depends on the subsystem as well as the set of
+    // public symbol names from linked objects.
+    // See LinkerDriver::findDefaultEntry from the LLD project for the flow chart.
+    const entry_name = switch (self.entry) {
+        .disabled => return null,
+        .default => switch (comp.config.output_mode) {
+            .Exe => "wWinMainCRTStartup",
+            .Obj, .Lib => return null,
+        },
+        .enabled => "wWinMainCRTStartup",
+        .named => |name| name,
+    };
     const global_index = self.resolver.get(entry_name) orelse return null;
     return self.globals.items[global_index];
 }
src/link/Elf.zig
@@ -25,6 +25,7 @@ linker_script: ?[]const u8,
 version_script: ?[]const u8,
 print_icf_sections: bool,
 print_map: bool,
+entry_name: ?[]const u8,
 
 ptr_width: PtrWidth,
 
@@ -290,6 +291,13 @@ pub fn createEmpty(
         .page_size = page_size,
         .default_sym_version = default_sym_version,
 
+        .entry_name = switch (options.entry) {
+            .disabled => null,
+            .default => if (output_mode != .Exe) null else defaultEntrySymbolName(target.cpu.arch),
+            .enabled => defaultEntrySymbolName(target.cpu.arch),
+            .named => |name| name,
+        },
+
         .image_base = b: {
             if (is_dyn_lib) break :b 0;
             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
 
     // Look for entry address in objects if not set by the incremental compiler.
     if (self.entry_index == null) {
-        if (comp.config.entry) |name| {
+        if (self.entry_name) |name| {
             self.entry_index = self.globalByName(name);
         }
     }
@@ -1679,9 +1687,8 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void {
             }
         }
 
-        if (comp.config.entry) |entry| {
-            try argv.append("--entry");
-            try argv.append(entry);
+        if (self.entry_name) |name| {
+            try argv.appendSlice(&.{ "--entry", name });
         }
 
         for (self.base.rpath_list) |rpath| {
@@ -2427,7 +2434,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
 
         // We can skip hashing libc and libc++ components that we are in charge of building from Zig
         // installation sources because they are always a product of the compiler version + target information.
-        man.hash.addOptionalBytes(comp.config.entry);
+        man.hash.addOptionalBytes(self.entry_name);
         man.hash.add(self.image_base);
         man.hash.add(self.base.gc_sections);
         man.hash.addOptional(self.sort_section);
@@ -2563,9 +2570,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
             .ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
         }
 
-        if (comp.config.entry) |entry| {
-            try argv.append("--entry");
-            try argv.append(entry);
+        if (self.entry_name) |name| {
+            try argv.appendSlice(&.{ "--entry", name });
         }
 
         for (self.base.force_undefined_symbols.keys()) |sym| {
@@ -6512,6 +6518,13 @@ const RelaSectionTable = std.AutoArrayHashMapUnmanaged(u32, RelaSection);
 pub const R_X86_64_ZIG_GOT32 = elf.R_X86_64_NUM + 1;
 pub const R_X86_64_ZIG_GOTPCREL = elf.R_X86_64_NUM + 2;
 
+fn defaultEntrySymbolName(cpu_arch: std.Target.Cpu.Arch) []const u8 {
+    return switch (cpu_arch) {
+        .mips, .mipsel, .mips64, .mips64el => "__start",
+        else => "_start",
+    };
+}
+
 const std = @import("std");
 const build_options = @import("build_options");
 const builtin = @import("builtin");
src/link/MachO.zig
@@ -1,4 +1,5 @@
 base: File,
+entry_name: ?[]const u8,
 
 /// If this is not null, an object file is created by LLVM and emitted to zcu_object_sub_path.
 llvm_object: ?*LlvmObject = null,
@@ -231,6 +232,12 @@ pub fn createEmpty(
         .install_name = options.install_name,
         .entitlements = options.entitlements,
         .compatibility_version = options.compatibility_version,
+        .entry_name = switch (options.entry) {
+            .disabled => null,
+            .default => if (output_mode != .Exe) null else default_entry_symbol_name,
+            .enabled => default_entry_symbol_name,
+            .named => |name| name,
+        },
     };
     if (use_llvm and comp.config.have_zcu) {
         self.llvm_object = try LlvmObject.create(arena, comp);
@@ -1629,8 +1636,9 @@ pub fn resolveSymbols(self: *MachO) !void {
     // we search for it in libraries should there be no object files specified
     // on the linker line.
     if (output_mode == .Exe) {
-        const entry_name = comp.config.entry.?;
-        _ = try self.addUndefined(entry_name, .{});
+        if (self.entry_name) |entry_name| {
+            _ = try self.addUndefined(entry_name, .{});
+        }
     }
 
     // Force resolution of any symbols requested by the user.
@@ -5085,8 +5093,7 @@ pub fn getStubsEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
 /// Returns symbol location corresponding to the set entrypoint if any.
 /// Asserts output mode is executable.
 pub fn getEntryPoint(self: MachO) ?SymbolWithLoc {
-    const comp = self.base.comp;
-    const entry_name = comp.config.entry orelse return null;
+    const entry_name = self.entry_name orelse return null;
     const global = self.getGlobal(entry_name) orelse return null;
     return global;
 }
@@ -5645,6 +5652,8 @@ pub fn logAtom(self: *MachO, atom_index: Atom.Index, logger: anytype) void {
     }
 }
 
+const default_entry_symbol_name = "_main";
+
 pub const base_tag: File.Tag = File.Tag.macho;
 pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1)));
 pub const N_BOUNDARY: u16 = @as(u16, @bitCast(@as(i16, -2)));
src/link/Wasm.zig
@@ -37,6 +37,7 @@ pub const Relocation = types.Relocation;
 pub const base_tag: link.File.Tag = .wasm;
 
 base: link.File,
+entry_name: ?[]const u8,
 import_symbols: bool,
 export_symbol_names: []const []const u8,
 global_base: ?u64,
@@ -397,6 +398,7 @@ pub fn createEmpty(
     const use_llvm = comp.config.use_llvm;
     const output_mode = comp.config.output_mode;
     const shared_memory = comp.config.shared_memory;
+    const wasi_exec_model = comp.config.wasi_exec_model;
 
     // If using LLD to link, this code should produce an object file so that it
     // can be passed to LLD.
@@ -434,6 +436,13 @@ pub fn createEmpty(
         .initial_memory = options.initial_memory,
         .max_memory = options.max_memory,
         .wasi_emulated_libs = options.wasi_emulated_libs,
+
+        .entry_name = switch (options.entry) {
+            .disabled => null,
+            .default => if (output_mode != .Exe) null else defaultEntrySymbolName(wasi_exec_model),
+            .enabled => defaultEntrySymbolName(wasi_exec_model),
+            .named => |name| name,
+        },
     };
     if (use_llvm and comp.config.have_zcu) {
         wasm.llvm_object = try LlvmObject.create(arena, comp);
@@ -3042,7 +3051,7 @@ fn setupExports(wasm: *Wasm) !void {
 fn setupStart(wasm: *Wasm) !void {
     const comp = wasm.base.comp;
     // do not export entry point if user set none or no default was set.
-    const entry_name = comp.config.entry orelse return;
+    const entry_name = wasm.entry_name orelse return;
 
     const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse {
         log.err("Entry symbol '{s}' missing, use '-fno-entry' to suppress", .{entry_name});
@@ -3475,8 +3484,8 @@ fn resetState(wasm: *Wasm) void {
 }
 
 pub fn flush(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
-    const use_lld = build_options.have_llvm and wasm.base.comp.config.use_lld;
-    const use_llvm = wasm.base.comp.config.use_llvm;
+    const use_lld = build_options.have_llvm and comp.config.use_lld;
+    const use_llvm = comp.config.use_llvm;
 
     if (use_lld) {
         return wasm.linkWithLLD(comp, prog_node);
@@ -3492,7 +3501,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = wasm.base.comp.gpa;
+    const gpa = comp.gpa;
     const shared_memory = comp.config.shared_memory;
     const import_memory = comp.config.import_memory;
 
@@ -3503,8 +3512,8 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
 
     const directory = wasm.base.emit.directory; // Just an alias to make it shorter to type.
     const full_out_path = try directory.join(arena, &[_][]const u8{wasm.base.emit.sub_path});
-    const opt_zcu = wasm.base.comp.module;
-    const use_llvm = wasm.base.comp.config.use_llvm;
+    const opt_zcu = comp.module;
+    const use_llvm = comp.config.use_llvm;
 
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
@@ -3535,7 +3544,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
     defer if (!wasm.base.disable_lld_caching) man.deinit();
     var digest: [Cache.hex_digest_len]u8 = undefined;
 
-    const objects = wasm.base.comp.objects;
+    const objects = comp.objects;
 
     // NOTE: The following section must be maintained to be equal
     // as the section defined in `linkWithLLD`
@@ -3556,7 +3565,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
         }
         try man.addOptionalFile(module_obj_path);
         try man.addOptionalFile(compiler_rt_path);
-        man.hash.addOptionalBytes(wasm.base.comp.config.entry);
+        man.hash.addOptionalBytes(wasm.entry_name);
         man.hash.add(wasm.base.stack_size);
         man.hash.add(wasm.base.build_id);
         man.hash.add(import_memory);
@@ -3605,12 +3614,12 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
     var positionals = std.ArrayList([]const u8).init(arena);
     try positionals.ensureUnusedCapacity(objects.len);
 
-    const target = wasm.base.comp.root_mod.resolved_target.result;
-    const output_mode = wasm.base.comp.config.output_mode;
-    const link_mode = wasm.base.comp.config.link_mode;
-    const link_libc = wasm.base.comp.config.link_libc;
-    const link_libcpp = wasm.base.comp.config.link_libcpp;
-    const wasi_exec_model = wasm.base.comp.config.wasi_exec_model;
+    const target = comp.root_mod.resolved_target.result;
+    const output_mode = comp.config.output_mode;
+    const link_mode = comp.config.link_mode;
+    const link_libc = comp.config.link_libc;
+    const link_libcpp = comp.config.link_libcpp;
+    const wasi_exec_model = comp.config.wasi_exec_model;
 
     // When the target os is WASI, we allow linking with WASI-LIBC
     if (target.os.tag == .wasi) {
@@ -4648,7 +4657,7 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
         }
         try man.addOptionalFile(module_obj_path);
         try man.addOptionalFile(compiler_rt_path);
-        man.hash.addOptionalBytes(wasm.base.comp.config.entry);
+        man.hash.addOptionalBytes(wasm.entry_name);
         man.hash.add(wasm.base.stack_size);
         man.hash.add(wasm.base.build_id);
         man.hash.add(import_memory);
@@ -4799,9 +4808,8 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
             try argv.append("--export-dynamic");
         }
 
-        if (comp.config.entry) |entry| {
-            try argv.append("--entry");
-            try argv.append(entry);
+        if (wasm.entry_name) |entry_name| {
+            try argv.appendSlice(&.{ "--entry", entry_name });
         } else {
             try argv.append("--no-entry");
         }
@@ -5347,3 +5355,10 @@ fn mark(wasm: *Wasm, loc: SymbolLoc) !void {
         try wasm.mark(target_loc.finalLoc(wasm));
     }
 }
+
+fn defaultEntrySymbolName(wasi_exec_model: std.builtin.WasiExecModel) []const u8 {
+    return switch (wasi_exec_model) {
+        .reactor => "_initialize",
+        .command => "_start",
+    };
+}
src/Compilation.zig
@@ -924,7 +924,7 @@ pub const LinkObject = struct {
     loption: bool = false,
 };
 
-pub const InitOptions = struct {
+pub const CreateOptions = struct {
     zig_lib_directory: Directory,
     local_cache_directory: Directory,
     global_cache_directory: Directory,
@@ -1054,7 +1054,7 @@ pub const InitOptions = struct {
     /// infinite recursion.
     skip_linker_dependencies: bool = false,
     hash_style: link.File.Elf.HashStyle = .both,
-    entry: ?[]const u8 = null,
+    entry: Entry = .default,
     force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{},
     stack_size: ?u64 = null,
     image_base: ?u64 = null,
@@ -1089,6 +1089,8 @@ pub const InitOptions = struct {
     /// (Windows) PDB output path
     pdb_out_path: ?[]const u8 = null,
     error_limit: ?Compilation.Module.ErrorInt = null,
+
+    pub const Entry = link.File.OpenOptions.Entry;
 };
 
 fn addModuleTableToCacheHash(
@@ -1159,7 +1161,7 @@ fn addModuleTableToCacheHash(
     }
 }
 
-pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
+pub fn create(gpa: Allocator, options: CreateOptions) !*Compilation {
     const output_mode = options.config.output_mode;
     const is_dyn_lib = switch (output_mode) {
         .Obj, .Exe => false,
@@ -1543,6 +1545,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
             .dynamicbase = options.linker_dynamicbase,
             .major_subsystem_version = options.major_subsystem_version,
             .minor_subsystem_version = options.minor_subsystem_version,
+            .entry = options.entry,
             .stack_size = options.stack_size,
             .image_base = options.image_base,
             .version_script = options.version_script,
src/link.zig
@@ -83,6 +83,8 @@ pub const File = struct {
         symbol_count_hint: u64 = 32,
         program_code_size_hint: u64 = 256 * 1024,
 
+        /// This may depend on what symbols are found during the linking process.
+        entry: Entry,
         /// Virtual address of the entry point procedure relative to image base.
         entry_addr: ?u64,
         stack_size: ?u64,
@@ -169,6 +171,13 @@ pub const File = struct {
         module_definition_file: ?[]const u8,
 
         wasi_emulated_libs: []const wasi_libc.CRTFile,
+
+        pub const Entry = union(enum) {
+            default,
+            disabled,
+            enabled,
+            named: []const u8,
+        };
     };
 
     /// Attempts incremental linking, if the file already exists. If
src/main.zig
@@ -857,6 +857,7 @@ fn buildOutputType(
     var linker_optimization: ?[]const u8 = null;
     var linker_module_definition_file: ?[]const u8 = null;
     var test_no_exec = false;
+    var entry: Compilation.CreateOptions.Entry = .default;
     var force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{};
     var stack_size: ?u64 = null;
     var image_base: ?u64 = null;
@@ -1129,7 +1130,7 @@ fn buildOutputType(
                     } else if (mem.eql(u8, arg, "-O")) {
                         mod_opts.optimize_mode = parseOptimizeMode(args_iter.nextOrFatal());
                     } else if (mem.startsWith(u8, arg, "-fentry=")) {
-                        create_module.opts.entry = .{ .named = arg["-fentry=".len..] };
+                        entry = .{ .named = arg["-fentry=".len..] };
                     } else if (mem.eql(u8, arg, "--force_undefined")) {
                         try force_undefined_symbols.put(arena, args_iter.nextOrFatal(), {});
                     } else if (mem.eql(u8, arg, "--stack")) {
@@ -1556,12 +1557,12 @@ fn buildOutputType(
                     } else if (mem.eql(u8, arg, "--import-memory")) {
                         create_module.opts.import_memory = true;
                     } else if (mem.eql(u8, arg, "-fentry")) {
-                        switch (create_module.opts.entry) {
-                            .default, .disabled => create_module.opts.entry = .enabled,
+                        switch (entry) {
+                            .default, .disabled => entry = .enabled,
                             .enabled, .named => {},
                         }
                     } else if (mem.eql(u8, arg, "-fno-entry")) {
-                        create_module.opts.entry = .disabled;
+                        entry = .disabled;
                     } else if (mem.eql(u8, arg, "--export-memory")) {
                         create_module.opts.export_memory = true;
                     } else if (mem.eql(u8, arg, "--import-symbols")) {
@@ -2065,7 +2066,7 @@ fn buildOutputType(
                         create_module.sysroot = it.only_arg;
                     },
                     .entry => {
-                        create_module.opts.entry = .{ .named = it.only_arg };
+                        entry = .{ .named = it.only_arg };
                     },
                     .force_undefined_symbol => {
                         try force_undefined_symbols.put(arena, it.only_arg, {});
@@ -2210,7 +2211,7 @@ fn buildOutputType(
                 } else if (mem.eql(u8, arg, "--export-table")) {
                     linker_export_table = true;
                 } else if (mem.eql(u8, arg, "--no-entry")) {
-                    create_module.opts.entry = .disabled;
+                    entry = .disabled;
                 } else if (mem.eql(u8, arg, "--initial-memory")) {
                     const next_arg = linker_args_it.nextOrFatal();
                     linker_initial_memory = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {
@@ -2284,7 +2285,7 @@ fn buildOutputType(
                     };
                     have_version = true;
                 } else if (mem.eql(u8, arg, "-e") or mem.eql(u8, arg, "--entry")) {
-                    create_module.opts.entry = .{ .named = linker_args_it.nextOrFatal() };
+                    entry = .{ .named = linker_args_it.nextOrFatal() };
                 } else if (mem.eql(u8, arg, "-u")) {
                     try force_undefined_symbols.put(arena, linker_args_it.nextOrFatal(), {});
                 } else if (mem.eql(u8, arg, "--stack") or mem.eql(u8, arg, "-stack_size")) {
@@ -3200,6 +3201,7 @@ fn buildOutputType(
         .minor_subsystem_version = minor_subsystem_version,
         .link_eh_frame_hdr = link_eh_frame_hdr,
         .link_emit_relocs = link_emit_relocs,
+        .entry = entry,
         .force_undefined_symbols = force_undefined_symbols,
         .stack_size = stack_size,
         .image_base = image_base,
@@ -3845,8 +3847,6 @@ fn createModule(
             error.ObjectFilesCannotShareMemory => fatal("object files cannot share memory", .{}),
             error.SharedMemoryRequiresAtomicsAndBulkMemory => fatal("shared memory requires atomics and bulk_memory CPU features", .{}),
             error.ThreadsRequireSharedMemory => fatal("threads require shared memory", .{}),
-            error.UnknownTargetEntryPoint => fatal("unknown target entry point", .{}),
-            error.NonExecutableEntryPoint => fatal("entry points only allowed for executables", .{}),
             error.EmittingLlvmModuleRequiresLlvmBackend => fatal("emitting an LLVM module requires using the LLVM backend", .{}),
             error.LlvmLacksTargetSupport => fatal("LLVM lacks support for the specified target", .{}),
             error.ZigLacksTargetSupport => fatal("compiler backend unavailable for the specified target", .{}),
src/target.zig
@@ -683,23 +683,3 @@ pub fn backendSupportsFeature(
         .safety_checked_instructions => use_llvm,
     };
 }
-
-pub fn defaultEntrySymbolName(
-    target: std.Target,
-    /// May be `undefined` when `target` is not WASI.
-    wasi_exec_model: std.builtin.WasiExecModel,
-) ?[]const u8 {
-    return switch (target.ofmt) {
-        .coff => "wWinMainCRTStartup",
-        .macho => "_main",
-        .elf, .plan9 => switch (target.cpu.arch) {
-            .mips, .mipsel, .mips64, .mips64el => "__start",
-            else => "_start",
-        },
-        .wasm => switch (wasi_exec_model) {
-            .reactor => "_initialize",
-            .command => "_start",
-        },
-        else => null,
-    };
-}