zig/src/Compilation/Config.zig
Andrew Kelley 44e2dbe117 fix logic for default entry point
when linking libc, the entry point is within libc.
when producing C code, the entry point is decided when compiling the C
code and does not need to be known up front.

fixes a false positive "error: unknown target entry point" when using
-ofmt=c.
2024-01-01 17:51:21 -07:00

522 lines
18 KiB
Zig

//! User-specified settings that have all the defaults resolved into concrete values.
have_zcu: bool,
output_mode: std.builtin.OutputMode,
link_mode: std.builtin.LinkMode,
link_libc: bool,
link_libcpp: bool,
link_libunwind: bool,
any_unwind_tables: bool,
any_c_source_files: bool,
any_non_single_threaded: bool,
/// This is true if any Module has error_tracing set to true. Function types
/// and function calling convention depend on this global value, however, other
/// kinds of error tracing are omitted depending on the per-Module setting.
any_error_tracing: bool,
any_sanitize_thread: bool,
pie: bool,
/// If this is true then linker code is responsible for making an LLVM IR
/// Module, outputting it to an object file, and then linking that together
/// with link options and other objects. Otherwise (depending on `use_lld`)
/// linker code directly outputs and updates the final binary.
use_llvm: bool,
/// Whether or not the LLVM library API will be used by the LLVM backend.
use_lib_llvm: bool,
/// If this is true then linker code is responsible for outputting an object
/// file and then using LLD to link it together with the link options and other
/// objects. Otherwise (depending on `use_llvm`) linker code directly outputs
/// and updates the final binary.
use_lld: bool,
c_frontend: CFrontend,
lto: bool,
/// WASI-only. Type of WASI execution model ("command" or "reactor").
/// Always set to `command` for non-WASI targets.
wasi_exec_model: std.builtin.WasiExecModel,
import_memory: bool,
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,
dll_export_fns: bool,
rdynamic: bool,
pub const CFrontend = enum { clang, aro };
pub const DebugFormat = union(enum) {
strip,
dwarf: std.dwarf.Format,
code_view,
};
pub const Options = struct {
output_mode: std.builtin.OutputMode,
resolved_target: Module.ResolvedTarget,
is_test: bool,
have_zcu: bool,
emit_bin: bool,
root_optimize_mode: ?std.builtin.OptimizeMode = null,
root_strip: ?bool = null,
root_error_tracing: ?bool = null,
link_mode: ?std.builtin.LinkMode = null,
ensure_libc_on_non_freestanding: bool = false,
ensure_libcpp_on_non_freestanding: bool = false,
any_non_single_threaded: bool = false,
any_sanitize_thread: bool = false,
any_unwind_tables: bool = false,
any_dyn_libs: bool = false,
any_c_source_files: bool = false,
any_non_stripped: bool = false,
any_error_tracing: bool = false,
emit_llvm_ir: bool = false,
emit_llvm_bc: bool = false,
link_libc: ?bool = null,
link_libcpp: ?bool = null,
link_libunwind: ?bool = null,
pie: ?bool = null,
use_llvm: ?bool = null,
use_lib_llvm: ?bool = null,
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,
export_memory: ?bool = null,
shared_memory: ?bool = null,
test_evented_io: bool = false,
debug_format: ?Config.DebugFormat = null,
dll_export_fns: ?bool = null,
rdynamic: ?bool = null,
};
pub const ResolveError = error{
WasiExecModelRequiresWasi,
SharedMemoryIsWasmOnly,
ObjectFilesCannotShareMemory,
SharedMemoryRequiresAtomicsAndBulkMemory,
ThreadsRequireSharedMemory,
UnknownTargetEntryPoint,
NonExecutableEntryPoint,
EmittingLlvmModuleRequiresLlvmBackend,
LlvmLacksTargetSupport,
ZigLacksTargetSupport,
EmittingBinaryRequiresLlvmLibrary,
LldIncompatibleObjectFormat,
LtoRequiresLld,
SanitizeThreadRequiresLibCpp,
LibCppRequiresLibUnwind,
OsRequiresLibC,
LibCppRequiresLibC,
LibUnwindRequiresLibC,
TargetCannotDynamicLink,
LibCRequiresDynamicLinking,
SharedLibrariesRequireDynamicLinking,
ExportMemoryAndDynamicIncompatible,
DynamicLibraryPrecludesPie,
TargetRequiresPie,
SanitizeThreadRequiresPie,
BackendLacksErrorTracing,
LlvmLibraryUnavailable,
LldUnavailable,
ClangUnavailable,
};
pub fn resolve(options: Options) ResolveError!Config {
const target = options.resolved_target.result;
// WASI-only. Resolve the optional exec-model option, defaults to command.
if (target.os.tag != .wasi and options.wasi_exec_model != null)
return error.WasiExecModelRequiresWasi;
const wasi_exec_model = options.wasi_exec_model orelse .command;
const shared_memory = b: {
if (!target.cpu.arch.isWasm()) {
if (options.shared_memory == true) return error.SharedMemoryIsWasmOnly;
break :b false;
}
if (options.output_mode == .Obj) {
if (options.shared_memory == true) return error.ObjectFilesCannotShareMemory;
break :b false;
}
if (!std.Target.wasm.featureSetHasAll(target.cpu.features, .{ .atomics, .bulk_memory })) {
if (options.shared_memory == true)
return error.SharedMemoryRequiresAtomicsAndBulkMemory;
break :b false;
}
if (options.any_non_single_threaded) {
if (options.shared_memory == false)
return error.ThreadsRequireSharedMemory;
break :b true;
}
break :b options.shared_memory orelse false;
};
// *If* the LLVM backend were to be selected, should Zig use the LLVM
// library to build the LLVM module?
const use_lib_llvm = b: {
if (!build_options.have_llvm) {
if (options.use_lib_llvm == true) return error.LlvmLibraryUnavailable;
break :b false;
}
break :b options.use_lib_llvm orelse true;
};
const root_optimize_mode = options.root_optimize_mode orelse .Debug;
// Make a decision on whether to use LLVM backend for machine code generation.
// Note that using the LLVM backend does not necessarily mean using LLVM libraries.
// For example, Zig can emit .bc and .ll files directly, and this is still considered
// using "the LLVM backend".
const use_llvm = b: {
// If emitting to LLVM bitcode object format, must use LLVM backend.
if (options.emit_llvm_ir or options.emit_llvm_bc) {
if (options.use_llvm == false)
return error.EmittingLlvmModuleRequiresLlvmBackend;
if (!target_util.hasLlvmSupport(target, target.ofmt))
return error.LlvmLacksTargetSupport;
break :b true;
}
// If LLVM does not support the target, then we can't use it.
if (!target_util.hasLlvmSupport(target, target.ofmt)) {
if (options.use_llvm == true) return error.LlvmLacksTargetSupport;
break :b false;
}
// If Zig does not support the target, then we can't use it.
if (target_util.zigBackend(target, false) == .other) {
if (options.use_llvm == false) return error.ZigLacksTargetSupport;
break :b true;
}
if (options.use_llvm) |x| break :b x;
// If we have no zig code to compile, no need for LLVM.
if (!options.have_zcu) break :b false;
// If we cannot use LLVM libraries, then our own backends will be a
// better default since the LLVM backend can only produce bitcode
// and not an object file or executable.
if (!use_lib_llvm) break :b false;
// Prefer LLVM for release builds.
if (root_optimize_mode != .Debug) break :b true;
// At this point we would prefer to use our own self-hosted backend,
// because the compilation speed is better than LLVM. But only do it if
// we are confident in the robustness of the backend.
break :b !target_util.selfHostedBackendIsAsRobustAsLlvm(target);
};
if (options.emit_bin) {
if (!use_lib_llvm and use_llvm) {
// Explicit request to use LLVM to produce an object file, but without
// using LLVM libraries. Impossible.
return error.EmittingBinaryRequiresLlvmLibrary;
}
if (target_util.zigBackend(target, use_llvm) == .other) {
// There is no compiler backend available for this target.
return error.ZigLacksTargetSupport;
}
}
// Make a decision on whether to use LLD or our own linker.
const use_lld = b: {
if (!target_util.hasLldSupport(target.ofmt)) {
if (options.use_lld == true) return error.LldIncompatibleObjectFormat;
break :b false;
}
if (!build_options.have_llvm) {
if (options.use_lld == true) return error.LldUnavailable;
break :b false;
}
if (options.lto == true) {
if (options.use_lld == false) return error.LtoRequiresLld;
break :b true;
}
if (options.use_lld) |x| break :b x;
break :b true;
};
// Make a decision on whether to use Clang or Aro for translate-c and compiling C files.
const c_frontend: CFrontend = b: {
if (!build_options.have_llvm) {
if (options.use_clang == true) return error.ClangUnavailable;
break :b .aro;
}
if (options.use_clang) |clang| {
break :b if (clang) .clang else .aro;
}
break :b .clang;
};
const lto = b: {
if (!use_lld) {
// zig ld LTO support is tracked by
// https://github.com/ziglang/zig/issues/8680
if (options.lto == true) return error.LtoRequiresLld;
break :b false;
}
if (options.lto) |x| break :b x;
if (!options.any_c_source_files) break :b false;
if (target.cpu.arch.isRISCV()) {
// Clang and LLVM currently don't support RISC-V target-abi for LTO.
// Compiling with LTO may fail or produce undesired results.
// See https://reviews.llvm.org/D71387
// See https://reviews.llvm.org/D102582
break :b false;
}
break :b switch (options.output_mode) {
.Lib, .Obj => false,
.Exe => switch (root_optimize_mode) {
.Debug => false,
.ReleaseSafe, .ReleaseFast, .ReleaseSmall => true,
},
};
};
const link_libcpp = b: {
if (options.link_libcpp == true) break :b true;
if (options.any_sanitize_thread) {
// TSAN is (for now...) implemented in C++ so it requires linking libc++.
if (options.link_libcpp == false) return error.SanitizeThreadRequiresLibCpp;
break :b true;
}
if (options.ensure_libcpp_on_non_freestanding and target.os.tag != .freestanding)
break :b true;
break :b false;
};
const link_libunwind = b: {
if (link_libcpp and target_util.libcNeedsLibUnwind(target)) {
if (options.link_libunwind == false) return error.LibCppRequiresLibUnwind;
break :b true;
}
break :b options.link_libunwind orelse false;
};
const link_libc = b: {
if (target_util.osRequiresLibC(target)) {
if (options.link_libc == false) return error.OsRequiresLibC;
break :b true;
}
if (link_libcpp) {
if (options.link_libc == false) return error.LibCppRequiresLibC;
break :b true;
}
if (link_libunwind) {
if (options.link_libc == false) return error.LibUnwindRequiresLibC;
break :b true;
}
if (options.link_libc) |x| break :b x;
if (options.ensure_libc_on_non_freestanding and target.os.tag != .freestanding)
break :b true;
break :b false;
};
const entry: ?[]const u8 = switch (options.entry) {
.disabled => null,
.default => b: {
if (options.output_mode != .Exe) break :b null;
// When linking libc, the entry point is inside libc and not in the
// zig compilation unit.
if (link_libc) 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);
const link_mode = b: {
const explicitly_exe_or_dyn_lib = switch (options.output_mode) {
.Obj => false,
.Lib => (options.link_mode orelse .Static) == .Dynamic,
.Exe => true,
};
if (target_util.cannotDynamicLink(target)) {
if (options.link_mode == .Dynamic) return error.TargetCannotDynamicLink;
break :b .Static;
}
if (explicitly_exe_or_dyn_lib and link_libc and
(target.isGnuLibC() or target_util.osRequiresLibC(target)))
{
if (options.link_mode == .Static) return error.LibCRequiresDynamicLinking;
break :b .Dynamic;
}
// When creating a executable that links to system libraries, we
// require dynamic linking, but we must not link static libraries
// or object files dynamically!
if (options.any_dyn_libs and options.output_mode == .Exe) {
if (options.link_mode == .Static) return error.SharedLibrariesRequireDynamicLinking;
break :b .Dynamic;
}
if (options.link_mode) |link_mode| break :b link_mode;
if (explicitly_exe_or_dyn_lib and link_libc and
options.resolved_target.is_native_abi and target.abi.isMusl())
{
// If targeting the system's native ABI and the system's libc is
// musl, link dynamically by default.
break :b .Dynamic;
}
// Static is generally a better default. Fight me.
break :b .Static;
};
const import_memory = options.import_memory orelse (options.output_mode == .Obj);
const export_memory = b: {
if (link_mode == .Dynamic) {
if (options.export_memory == true) return error.ExportMemoryAndDynamicIncompatible;
break :b false;
}
if (options.export_memory) |x| break :b x;
break :b !import_memory;
};
const pie: bool = b: {
switch (options.output_mode) {
.Obj, .Exe => {},
.Lib => if (link_mode == .Dynamic) {
if (options.pie == true) return error.DynamicLibraryPrecludesPie;
break :b false;
},
}
if (target_util.requiresPIE(target)) {
if (options.pie == false) return error.TargetRequiresPie;
break :b true;
}
if (options.any_sanitize_thread) {
if (options.pie == false) return error.SanitizeThreadRequiresPie;
break :b true;
}
if (options.pie) |pie| break :b pie;
break :b false;
};
const root_strip = b: {
if (options.root_strip) |x| break :b x;
if (root_optimize_mode == .ReleaseSmall) break :b true;
if (!target_util.hasDebugInfo(target)) break :b true;
break :b false;
};
const debug_format: DebugFormat = b: {
if (root_strip and !options.any_non_stripped) break :b .strip;
break :b switch (target.ofmt) {
.elf, .macho, .wasm => .{ .dwarf = .@"32" },
.coff => .code_view,
.c => switch (target.os.tag) {
.windows, .uefi => .code_view,
else => .{ .dwarf = .@"32" },
},
.spirv, .nvptx, .dxcontainer, .hex, .raw, .plan9 => .strip,
};
};
const backend_supports_error_tracing = target_util.backendSupportsFeature(
target.cpu.arch,
target.ofmt,
use_llvm,
.error_return_trace,
);
const root_error_tracing = b: {
if (options.root_error_tracing) |x| break :b x;
if (root_strip) break :b false;
if (!backend_supports_error_tracing) break :b false;
break :b switch (root_optimize_mode) {
.Debug => true,
.ReleaseSafe, .ReleaseFast, .ReleaseSmall => false,
};
};
const any_error_tracing = root_error_tracing or options.any_error_tracing;
if (any_error_tracing and !backend_supports_error_tracing)
return error.BackendLacksErrorTracing;
const rdynamic = options.rdynamic orelse false;
const dll_export_fns = b: {
if (options.dll_export_fns) |x| break :b x;
if (rdynamic) break :b true;
break :b switch (options.output_mode) {
.Obj, .Exe => false,
.Lib => link_mode == .Dynamic,
};
};
return .{
.output_mode = options.output_mode,
.have_zcu = options.have_zcu,
.is_test = options.is_test,
.test_evented_io = options.test_evented_io,
.link_mode = link_mode,
.link_libc = link_libc,
.link_libcpp = link_libcpp,
.link_libunwind = link_libunwind,
.any_unwind_tables = any_unwind_tables,
.any_c_source_files = options.any_c_source_files,
.any_non_single_threaded = options.any_non_single_threaded,
.any_error_tracing = any_error_tracing,
.any_sanitize_thread = options.any_sanitize_thread,
.root_error_tracing = root_error_tracing,
.pie = pie,
.lto = lto,
.import_memory = import_memory,
.export_memory = export_memory,
.shared_memory = shared_memory,
.c_frontend = c_frontend,
.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,
.dll_export_fns = dll_export_fns,
.rdynamic = rdynamic,
};
}
const std = @import("std");
const Module = @import("../Package.zig").Module;
const Config = @This();
const target_util = @import("../target.zig");
const build_options = @import("build_options");