diff --git a/CMakeLists.txt b/CMakeLists.txt index b3004d3254be9e16979455d70b49bdd475ec5df6..202c5d1861aa89bd5147c454e17492a709005681 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -521,6 +521,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/Air.zig" "${CMAKE_SOURCE_DIR}/src/AstGen.zig" "${CMAKE_SOURCE_DIR}/src/Compilation.zig" + "${CMAKE_SOURCE_DIR}/src/Compilation/Config.zig" "${CMAKE_SOURCE_DIR}/src/Liveness.zig" "${CMAKE_SOURCE_DIR}/src/Module.zig" "${CMAKE_SOURCE_DIR}/src/Package.zig" diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index b7865e730b5e8feb429ccf8f55324d9ad98f4f4f..18c1ffa07a2aa3434bbb011ae98c3db456a12113 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -312,6 +312,20 @@ pub const HashHelper = struct { ) catch unreachable; return out_digest; } + + pub fn oneShot(bytes: []const u8) [hex_digest_len]u8 { + var hasher: Hasher = hasher_init; + hasher.update(bytes); + var bin_digest: BinDigest = undefined; + hasher.final(&bin_digest); + var out_digest: [hex_digest_len]u8 = undefined; + _ = fmt.bufPrint( + &out_digest, + "{s}", + .{fmt.fmtSliceHexLower(&bin_digest)}, + ) catch unreachable; + return out_digest; + } }; pub const Lock = struct { diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 81fab2be5e69b53679422e6e1f55f8c208bf93ea..9daa89deb8d5b5c047d5062b06654ddbccec64c6 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -34,6 +34,7 @@ valgrind: ?bool, pic: ?bool, red_zone: ?bool, omit_frame_pointer: ?bool, +error_tracing: ?bool, link_libc: ?bool, link_libcpp: ?bool, @@ -177,6 +178,7 @@ pub const CreateOptions = struct { /// Whether to omit the stack frame pointer. Frees up a register and makes it /// more difficult to obtain stack traces. Has target-dependent effects. omit_frame_pointer: ?bool = null, + error_tracing: ?bool = null, }; pub const Import = struct { @@ -216,6 +218,7 @@ pub fn init(m: *Module, owner: *std.Build, options: CreateOptions, compile: ?*St .pic = options.pic, .red_zone = options.red_zone, .omit_frame_pointer = options.omit_frame_pointer, + .error_tracing = options.error_tracing, .export_symbol_names = &.{}, }; @@ -601,6 +604,7 @@ pub fn appendZigProcessFlags( try addFlag(zig_args, m.stack_check, "-fstack-check", "-fno-stack-check"); try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector"); try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer"); + try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing"); try addFlag(zig_args, m.sanitize_c, "-fsanitize-c", "-fno-sanitize-c"); try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread"); try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind"); diff --git a/src/Builtin.zig b/src/Builtin.zig new file mode 100644 index 0000000000000000000000000000000000000000..7224a6fd24fb04592bac33aa78373dc9cbfe6e47 --- /dev/null +++ b/src/Builtin.zig @@ -0,0 +1,240 @@ +target: std.Target, +zig_backend: std.builtin.CompilerBackend, +output_mode: std.builtin.OutputMode, +link_mode: std.builtin.LinkMode, +is_test: bool, +test_evented_io: bool, +single_threaded: bool, +link_libc: bool, +link_libcpp: bool, +optimize_mode: std.builtin.OptimizeMode, +error_tracing: bool, +valgrind: bool, +sanitize_thread: bool, +pic: bool, +pie: bool, +strip: bool, +code_model: std.builtin.CodeModel, +omit_frame_pointer: bool, +wasi_exec_model: std.builtin.WasiExecModel, + +pub fn generate(opts: @This(), allocator: Allocator) Allocator.Error![:0]u8 { + var buffer = std.ArrayList(u8).init(allocator); + defer buffer.deinit(); + + const target = opts.target; + const generic_arch_name = target.cpu.arch.genericName(); + const zig_backend = opts.zig_backend; + + @setEvalBranchQuota(4000); + try buffer.writer().print( + \\const std = @import("std"); + \\/// Zig version. When writing code that supports multiple versions of Zig, prefer + \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks. + \\pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable; + \\pub const zig_version_string = "{s}"; + \\pub const zig_backend = std.builtin.CompilerBackend.{}; + \\ + \\pub const output_mode = std.builtin.OutputMode.{}; + \\pub const link_mode = std.builtin.LinkMode.{}; + \\pub const is_test = {}; + \\pub const single_threaded = {}; + \\pub const abi = std.Target.Abi.{}; + \\pub const cpu: std.Target.Cpu = .{{ + \\ .arch = .{}, + \\ .model = &std.Target.{}.cpu.{}, + \\ .features = std.Target.{}.featureSet(&[_]std.Target.{}.Feature{{ + \\ + , .{ + build_options.version, + std.zig.fmtId(@tagName(zig_backend)), + std.zig.fmtId(@tagName(opts.output_mode)), + std.zig.fmtId(@tagName(opts.link_mode)), + opts.is_test, + opts.single_threaded, + std.zig.fmtId(@tagName(target.abi)), + std.zig.fmtId(@tagName(target.cpu.arch)), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(target.cpu.model.name), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(generic_arch_name), + }); + + for (target.cpu.arch.allFeaturesList(), 0..) |feature, index_usize| { + const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize)); + const is_enabled = target.cpu.features.isEnabled(index); + if (is_enabled) { + try buffer.writer().print(" .{},\n", .{std.zig.fmtId(feature.name)}); + } + } + try buffer.writer().print( + \\ }}), + \\}}; + \\pub const os = std.Target.Os{{ + \\ .tag = .{}, + \\ .version_range = .{{ + , + .{std.zig.fmtId(@tagName(target.os.tag))}, + ); + + switch (target.os.getVersionRange()) { + .none => try buffer.appendSlice(" .none = {} },\n"), + .semver => |semver| try buffer.writer().print( + \\ .semver = .{{ + \\ .min = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ .max = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ }}}}, + \\ + , .{ + semver.min.major, + semver.min.minor, + semver.min.patch, + + semver.max.major, + semver.max.minor, + semver.max.patch, + }), + .linux => |linux| try buffer.writer().print( + \\ .linux = .{{ + \\ .range = .{{ + \\ .min = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ .max = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ }}, + \\ .glibc = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ }}}}, + \\ + , .{ + linux.range.min.major, + linux.range.min.minor, + linux.range.min.patch, + + linux.range.max.major, + linux.range.max.minor, + linux.range.max.patch, + + linux.glibc.major, + linux.glibc.minor, + linux.glibc.patch, + }), + .windows => |windows| try buffer.writer().print( + \\ .windows = .{{ + \\ .min = {s}, + \\ .max = {s}, + \\ }}}}, + \\ + , + .{ windows.min, windows.max }, + ), + } + try buffer.appendSlice( + \\}; + \\pub const target: std.Target = .{ + \\ .cpu = cpu, + \\ .os = os, + \\ .abi = abi, + \\ .ofmt = object_format, + \\ + ); + + if (target.dynamic_linker.get()) |dl| { + try buffer.writer().print( + \\ .dynamic_linker = std.Target.DynamicLinker.init("{s}"), + \\}}; + \\ + , .{dl}); + } else { + try buffer.appendSlice( + \\ .dynamic_linker = std.Target.DynamicLinker.none, + \\}; + \\ + ); + } + + // This is so that compiler_rt and libc.zig libraries know whether they + // will eventually be linked with libc. They make different decisions + // about what to export depending on whether another libc will be linked + // in. For example, compiler_rt will not export the __chkstk symbol if it + // knows libc will provide it, and likewise c.zig will not export memcpy. + const link_libc = opts.link_libc; + + try buffer.writer().print( + \\pub const object_format = std.Target.ObjectFormat.{}; + \\pub const mode = std.builtin.OptimizeMode.{}; + \\pub const link_libc = {}; + \\pub const link_libcpp = {}; + \\pub const have_error_return_tracing = {}; + \\pub const valgrind_support = {}; + \\pub const sanitize_thread = {}; + \\pub const position_independent_code = {}; + \\pub const position_independent_executable = {}; + \\pub const strip_debug_info = {}; + \\pub const code_model = std.builtin.CodeModel.{}; + \\pub const omit_frame_pointer = {}; + \\ + , .{ + std.zig.fmtId(@tagName(target.ofmt)), + std.zig.fmtId(@tagName(opts.optimize_mode)), + link_libc, + opts.link_libcpp, + opts.error_tracing, + opts.valgrind, + opts.sanitize_thread, + opts.pic, + opts.pie, + opts.strip, + std.zig.fmtId(@tagName(opts.code_model)), + opts.omit_frame_pointer, + }); + + if (target.os.tag == .wasi) { + const wasi_exec_model_fmt = std.zig.fmtId(@tagName(opts.wasi_exec_model)); + try buffer.writer().print( + \\pub const wasi_exec_model = std.builtin.WasiExecModel.{}; + \\ + , .{wasi_exec_model_fmt}); + } + + if (opts.is_test) { + try buffer.appendSlice( + \\pub var test_functions: []const std.builtin.TestFn = undefined; // overwritten later + \\ + ); + if (opts.test_evented_io) { + try buffer.appendSlice( + \\pub const test_io_mode = .evented; + \\ + ); + } else { + try buffer.appendSlice( + \\pub const test_io_mode = .blocking; + \\ + ); + } + } + + return buffer.toOwnedSliceSentinel(0); +} + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const build_options = @import("build_options"); diff --git a/src/Compilation.zig b/src/Compilation.zig index 4f65dc22f2fca5f5aba833a67d6dcbee68202f50..ca43e2da079ffed98726c736ed5130cb11f759de 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -38,12 +38,43 @@ const Autodoc = @import("Autodoc.zig"); const Color = @import("main.zig").Color; const resinator = @import("resinator.zig"); +pub const Config = @import("Compilation/Config.zig"); + /// General-purpose allocator. Used for both temporary and long-term storage. gpa: Allocator, /// Arena-allocated memory, mostly used during initialization. However, it can be used /// for other things requiring the same lifetime as the `Compilation`. arena: std.heap.ArenaAllocator, -bin_file: *link.File, +/// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. +/// TODO: rename to zcu: ?*Zcu +module: ?*Module, +/// All compilations have a root module because this is where some important +/// settings are stored, such as target and optimization mode. This module +/// might not have any .zig code associated with it, however. +root_mod: *Package.Module, + +/// User-specified settings that have all the defaults resolved into concrete values. +config: Config, + +/// This is `null` when `-fno-emit-bin` is used. +bin_file: ?*link.File, + +/// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin) +sysroot: ?[]const u8, +/// This is `null` when not building a Windows DLL, or when `-fno-emit-implib` is used. +implib_emit: ?Emit, +/// This is non-null when `-femit-docs` is provided. +docs_emit: ?Emit, +root_name: [:0]const u8, +cache_mode: CacheMode, +include_compiler_rt: bool, +objects: []Compilation.LinkObject, +/// These are *always* dynamically linked. Static libraries will be +/// provided as positional arguments. +system_libs: std.StringArrayHashMapUnmanaged(SystemLib), +version: ?std.SemanticVersion, +libc_installation: ?*const LibCInstallation, + c_object_table: std.AutoArrayHashMapUnmanaged(*CObject, void) = .{}, win32_resource_table: if (build_options.only_core_functionality) void else std.AutoArrayHashMapUnmanaged(*Win32Resource, void) = if (build_options.only_core_functionality) {} else .{}, @@ -87,8 +118,6 @@ failed_win32_resources: if (build_options.only_core_functionality) void else std misc_failures: std.AutoArrayHashMapUnmanaged(MiscTask, MiscError) = .{}, keep_source_files_loaded: bool, -c_frontend: CFrontend, -sanitize_c: bool, /// When this is `true` it means invoking clang as a sub-process is expected to inherit /// stdin, stdout, stderr, and if it returns non success, to forward the exit code. /// Otherwise we attempt to parse the error messages and expose them via the Compilation API. @@ -107,8 +136,6 @@ verbose_llvm_cpu_features: bool, disable_c_depfile: bool, time_report: bool, stack_report: bool, -unwind_tables: bool, -test_evented_io: bool, debug_compiler_runtime_libs: bool, debug_compile_errors: bool, job_queued_compiler_rt_lib: bool = false, @@ -118,7 +145,6 @@ formatted_panics: bool = false, last_update_was_cache_hit: bool = false, c_source_files: []const CSourceFile, -clang_argv: []const []const u8, rc_source_files: []const RcSourceFile, cache_parent: *Cache, /// Path to own executable for invoking `zig clang`. @@ -194,7 +220,29 @@ emit_llvm_bc: ?EmitLoc, work_queue_wait_group: WaitGroup = .{}, astgen_wait_group: WaitGroup = .{}, -pub const default_stack_protector_buffer_size = 4; +pub const Emit = struct { + /// Where the output will go. + directory: Directory, + /// Path to the output file, relative to `directory`. + sub_path: []const u8, + + /// Returns the full path to `basename` if it were in the same directory as the + /// `Emit` sub_path. + pub fn basenamePath(emit: Emit, arena: Allocator, basename: [:0]const u8) ![:0]const u8 { + const full_path = if (emit.directory.path) |p| + try std.fs.path.join(arena, &[_][]const u8{ p, emit.sub_path }) + else + emit.sub_path; + + if (std.fs.path.dirname(full_path)) |dirname| { + return try std.fs.path.joinZ(arena, &.{ dirname, basename }); + } else { + return basename; + } + } +}; + +pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size; pub const SemaError = Module.SemaError; pub const CRTFile = struct { @@ -208,8 +256,8 @@ pub const CRTFile = struct { } }; -// supported languages for "zig clang -x ". -// Loosely based on llvm-project/clang/include/clang/Driver/Types.def +/// Supported languages for "zig clang -x ". +/// Loosely based on llvm-project/clang/include/clang/Driver/Types.def pub const LangToExt = std.ComptimeStringMap(FileExt, .{ .{ "c", .c }, .{ "c-header", .h }, @@ -226,16 +274,20 @@ pub const LangToExt = std.ComptimeStringMap(FileExt, .{ /// For passing to a C compiler. pub const CSourceFile = struct { + /// Many C compiler flags are determined by settings contained in the owning Module. + owner: *Package.Module, src_path: []const u8, extra_flags: []const []const u8 = &.{}, /// Same as extra_flags except they are not added to the Cache hash. cache_exempt_flags: []const []const u8 = &.{}, - // this field is non-null iff language was explicitly set with "-x lang". + /// This field is non-null if and only if the language was explicitly set + /// with "-x lang". ext: ?FileExt = null, }; /// For passing to resinator. pub const RcSourceFile = struct { + owner: *Package.Module, src_path: []const u8, extra_flags: []const []const u8 = &.{}, }; @@ -742,6 +794,22 @@ pub const EmitLoc = struct { }; pub const cache_helpers = struct { + pub fn addResolvedTarget( + hh: *Cache.HashHelper, + resolved_target: Package.Module.ResolvedTarget, + ) void { + const target = resolved_target.result; + hh.add(target.cpu.arch); + hh.addBytes(target.cpu.model.name); + hh.add(target.cpu.features.ints); + hh.add(target.os.tag); + hh.add(target.os.getVersionRange()); + hh.add(target.abi); + hh.add(target.ofmt); + hh.add(resolved_target.is_native_os); + hh.add(resolved_target.is_native_abi); + } + pub fn addEmitLoc(hh: *Cache.HashHelper, emit_loc: EmitLoc) void { hh.addBytes(emit_loc.basename); } @@ -751,7 +819,7 @@ pub const cache_helpers = struct { addEmitLoc(hh, optional_emit_loc orelse return); } - pub fn hashCSource(self: *Cache.Manifest, c_source: Compilation.CSourceFile) !void { + pub fn hashCSource(self: *Cache.Manifest, c_source: CSourceFile) !void { _ = try self.addFile(c_source.src_path, null); // Hash the extra flags, with special care to call addFile for file parameters. // TODO this logic can likely be improved by utilizing clang_options_data.zig. @@ -770,8 +838,6 @@ pub const cache_helpers = struct { } }; -pub const CFrontend = enum { clang, aro }; - pub const ClangPreprocessorMode = enum { no, /// This means we are doing `zig cc -E -o `. @@ -798,11 +864,21 @@ pub const InitOptions = struct { zig_lib_directory: Directory, local_cache_directory: Directory, global_cache_directory: Directory, - target: Target, - root_name: []const u8, - main_mod: ?*Package.Module, - output_mode: std.builtin.OutputMode, thread_pool: *ThreadPool, + self_exe_path: ?[]const u8 = null, + + /// Options that have been resolved by calling `resolveDefaults`. + config: Compilation.Config, + + root_mod: *Package.Module, + /// Normally, `main_mod` and `root_mod` are the same. The exception is `zig + /// test`, in which `root_mod` is the test runner, and `main_mod` is the + /// user's source file which has the tests. + main_mod: ?*Package.Module, + /// This is provided so that the API user has a chance to tweak the + /// per-module settings of the standard library. + std_mod: *Package.Module, + root_name: []const u8, sysroot: ?[]const u8 = null, /// `null` means to not emit a binary file. emit_bin: ?EmitLoc, @@ -818,7 +894,6 @@ pub const InitOptions = struct { emit_docs: ?EmitLoc = null, /// `null` means to not emit an import lib. emit_implib: ?EmitLoc = null, - link_mode: ?std.builtin.LinkMode = null, dll_export_fns: ?bool = false, /// Normally when using LLD to link, Zig uses a file named "lld.id" in the /// same directory as the output binary which contains the hash of the link @@ -828,14 +903,12 @@ pub const InitOptions = struct { /// this flag would be set to disable this machinery to avoid false positives. disable_lld_caching: bool = false, cache_mode: CacheMode = .incremental, - optimize_mode: std.builtin.OptimizeMode = .Debug, keep_source_files_loaded: bool = false, - clang_argv: []const []const u8 = &[0][]const u8{}, lib_dirs: []const []const u8 = &[0][]const u8{}, rpath_list: []const []const u8 = &[0][]const u8{}, symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{}, - c_source_files: []const CSourceFile = &[0]CSourceFile{}, - rc_source_files: []const RcSourceFile = &[0]RcSourceFile{}, + c_source_files: []const CSourceFile = &.{}, + rc_source_files: []const RcSourceFile = &.{}, manifest_file: ?[]const u8 = null, rc_includes: RcIncludes = .any, link_objects: []LinkObject = &[0]LinkObject{}, @@ -849,40 +922,16 @@ pub const InitOptions = struct { /// * mman /// * signal wasi_emulated_libs: []const wasi_libc.CRTFile = &[0]wasi_libc.CRTFile{}, - link_libc: bool = false, - link_libcpp: bool = false, - link_libunwind: bool = false, - want_pic: ?bool = null, /// This means that if the output mode is an executable it will be a /// Position Independent Executable. If the output mode is not an /// executable this field is ignored. - want_pie: ?bool = null, - want_sanitize_c: ?bool = null, - want_stack_check: ?bool = null, - /// null means default. - /// 0 means no stack protector. - /// other number means stack protection with that buffer size. - want_stack_protector: ?u32 = null, - want_red_zone: ?bool = null, - omit_frame_pointer: ?bool = null, - want_valgrind: ?bool = null, - want_tsan: ?bool = null, want_compiler_rt: ?bool = null, want_lto: ?bool = null, - want_unwind_tables: ?bool = null, - use_llvm: ?bool = null, - use_lib_llvm: ?bool = null, - use_lld: ?bool = null, - use_clang: ?bool = null, - single_threaded: ?bool = null, - strip: ?bool = null, formatted_panics: ?bool = null, rdynamic: bool = false, function_sections: bool = false, data_sections: bool = false, no_builtin: bool = false, - is_native_os: bool, - is_native_abi: bool, time_report: bool = false, stack_report: bool = false, link_eh_frame_hdr: bool = false, @@ -893,14 +942,11 @@ pub const InitOptions = struct { linker_gc_sections: ?bool = null, linker_allow_shlib_undefined: ?bool = null, linker_bind_global_refs_locally: ?bool = null, - linker_import_memory: ?bool = null, - linker_export_memory: ?bool = null, linker_import_symbols: bool = false, linker_import_table: bool = false, linker_export_table: bool = false, linker_initial_memory: ?u64 = null, linker_max_memory: ?u64 = null, - linker_shared_memory: bool = false, linker_global_base: ?u64 = null, linker_export_symbol_names: []const []const u8 = &.{}, linker_print_gc_sections: bool = false, @@ -938,8 +984,6 @@ pub const InitOptions = struct { verbose_llvm_bc: ?[]const u8 = null, verbose_cimport: bool = false, verbose_llvm_cpu_features: bool = false, - is_test: bool = false, - test_evented_io: bool = false, debug_compiler_runtime_libs: bool = false, debug_compile_errors: bool = false, /// Normally when you create a `Compilation`, Zig will automatically build @@ -952,23 +996,18 @@ pub const InitOptions = struct { force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{}, stack_size_override: ?u64 = null, image_base_override: ?u64 = null, - self_exe_path: ?[]const u8 = null, version: ?std.SemanticVersion = null, compatibility_version: ?std.SemanticVersion = null, libc_installation: ?*const LibCInstallation = null, - machine_code_model: std.builtin.CodeModel = .default, clang_preprocessor_mode: ClangPreprocessorMode = .no, /// This is for stage1 and should be deleted upon completion of self-hosting. color: Color = .auto, reference_trace: ?u32 = null, - error_tracing: ?bool = null, test_filter: ?[]const u8 = null, test_name_prefix: ?[]const u8 = null, test_runner_path: ?[]const u8 = null, subsystem: ?std.Target.SubSystem = null, dwarf_format: ?std.dwarf.Format = null, - /// WASI-only. Type of WASI execution model ("command" or "reactor"). - wasi_exec_model: ?std.builtin.WasiExecModel = null, /// (Zig compiler development) Enable dumping linker's state as JSON. enable_link_snapshots: bool = false, /// (Darwin) Install name of the dylib @@ -989,77 +1028,93 @@ pub const InitOptions = struct { pdb_source_path: ?[]const u8 = null, /// (Windows) PDB output path pdb_out_path: ?[]const u8 = null, - error_limit: ?Module.ErrorInt = null, + error_limit: ?Compilation.Module.ErrorInt = null, /// (SPIR-V) whether to generate a structured control flow graph or not want_structured_cfg: ?bool = null, }; fn addModuleTableToCacheHash( + gpa: Allocator, + arena: Allocator, hash: *Cache.HashHelper, - arena: *std.heap.ArenaAllocator, - mod_table: Package.Module.Deps, - seen_table: *std.AutoHashMap(*Package.Module, void), + root_mod: *Package.Module, hash_type: union(enum) { path_bytes, files: *Cache.Manifest }, ) (error{OutOfMemory} || std.os.GetCwdError)!void { - const allocator = arena.allocator(); + var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, void) = .{}; + try seen_table.put(gpa, root_mod, {}); - const module_indices = try allocator.alloc(u32, mod_table.count()); - // Copy over the hashmap entries to our slice - for (module_indices, 0..) |*module_index, index| module_index.* = @intCast(index); - // Sort the slice by package name - mem.sortUnstable(u32, module_indices, &mod_table, struct { - fn lessThan(deps: *const Package.Module.Deps, lhs: u32, rhs: u32) bool { - const keys = deps.keys(); - return std.mem.lessThan(u8, keys[lhs], keys[rhs]); + const SortByName = struct { + names: []const []const u8, + + pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool { + const lhs_key = ctx.names[lhs_index]; + const rhs_key = ctx.names[rhs_index]; + return mem.lessThan(u8, lhs_key, rhs_key); } - }.lessThan); + }; - for (module_indices) |module_index| { - const module = mod_table.values()[module_index]; - if ((try seen_table.getOrPut(module)).found_existing) continue; + var i: usize = 0; + while (i < seen_table.count()) : (i += 1) { + const mod = seen_table.keys()[i]; + + cache_helpers.addResolvedTarget(hash, mod.resolved_target); + hash.add(mod.optimize_mode); + hash.add(mod.code_model); + hash.add(mod.single_threaded); + hash.add(mod.error_tracing); + hash.add(mod.valgrind); + hash.add(mod.pic); + hash.add(mod.strip); + hash.add(mod.omit_frame_pointer); + hash.add(mod.stack_check); + hash.add(mod.red_zone); + hash.add(mod.sanitize_c); + hash.add(mod.sanitize_thread); - // Finally insert the package name and path to the cache hash. - hash.addBytes(mod_table.keys()[module_index]); switch (hash_type) { .path_bytes => { - hash.addBytes(module.root_src_path); - hash.addOptionalBytes(module.root.root_dir.path); - hash.addBytes(module.root.sub_path); + hash.addBytes(mod.root_src_path); + hash.addOptionalBytes(mod.root.root_dir.path); + hash.addBytes(mod.root.sub_path); }, .files => |man| { - const pkg_zig_file = try module.root.joinString( - allocator, - module.root_src_path, - ); + const pkg_zig_file = try mod.root.joinString(arena, mod.root_src_path); _ = try man.addFile(pkg_zig_file, null); }, } - // Recurse to handle the module's dependencies - try addModuleTableToCacheHash(hash, arena, module.deps, seen_table, hash_type); + + mod.deps.sortUnstable(SortByName{ .names = mod.deps.keys() }); + + hash.addListOfBytes(mod.deps.keys()); + + const deps = mod.deps.values(); + try seen_table.ensureUnusedCapacity(gpa, deps.len); + for (deps) |dep| seen_table.putAssumeCapacity(dep, {}); } } pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { - const is_dyn_lib = switch (options.output_mode) { + const output_mode = options.config.output_mode; + const is_dyn_lib = switch (output_mode) { .Obj, .Exe => false, - .Lib => (options.link_mode orelse .Static) == .Dynamic, + .Lib => options.config.link_mode == .Dynamic, }; - const is_exe_or_dyn_lib = switch (options.output_mode) { + const is_exe_or_dyn_lib = switch (output_mode) { .Obj => false, .Lib => is_dyn_lib, .Exe => true, }; - // WASI-only. Resolve the optional exec-model option, defaults to command. - const wasi_exec_model = if (options.target.os.tag != .wasi) undefined else options.wasi_exec_model orelse .command; - if (options.linker_export_table and options.linker_import_table) { return error.ExportTableAndImportTableConflict; } + const have_zcu = options.root_mod.root_src_path.len != 0; + const comp: *Compilation = comp: { - // For allocations that have the same lifetime as Compilation. This arena is used only during this - // initialization and then is freed in deinit(). + // For allocations that have the same lifetime as Compilation. This + // arena is used only during this initialization and then is freed in + // deinit(). var arena_allocator = std.heap.ArenaAllocator.init(gpa); errdefer arena_allocator.deinit(); const arena = arena_allocator.allocator(); @@ -1069,366 +1124,145 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const comp = try arena.create(Compilation); const root_name = try arena.dupeZ(u8, options.root_name); - // Make a decision on whether to use LLVM or our own backend. - const use_lib_llvm = options.use_lib_llvm orelse build_options.have_llvm; - const use_llvm = blk: { - if (options.use_llvm) |explicit| - break :blk explicit; - - // If emitting to LLVM bitcode object format, must use LLVM backend. - if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) - break :blk true; - - // If we have no zig code to compile, no need for LLVM. - if (options.main_mod == null) - break :blk 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 :blk false; - - // If LLVM does not support the target, then we can't use it. - if (!target_util.hasLlvmSupport(options.target, options.target.ofmt)) - break :blk false; - - // Prefer LLVM for release builds. - if (options.optimize_mode != .Debug) - break :blk 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 :blk !target_util.selfHostedBackendIsAsRobustAsLlvm(options.target); - }; - if (!use_llvm) { - if (options.use_llvm == true) { - return error.ZigCompilerNotBuiltWithLLVMExtensions; - } - if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) { - return error.EmittingLlvmModuleRequiresUsingLlvmBackend; - } - } + const use_llvm = options.config.use_llvm; // TODO: once we support incremental compilation for the LLVM backend via // saving the LLVM module into a bitcode file and restoring it, along with // compiler state, the second clause here can be removed so that incremental // cache mode is used for LLVM backend too. We need some fuzz testing before // that can be enabled. - const cache_mode = if ((use_llvm or options.main_mod == null) and !options.disable_lld_caching) + const cache_mode = if ((use_llvm or !have_zcu) and !options.disable_lld_caching) CacheMode.whole else options.cache_mode; - const tsan = options.want_tsan orelse false; - // TSAN is implemented in C++ so it requires linking libc++. - const link_libcpp = options.link_libcpp or tsan; - const link_libc = link_libcpp or options.link_libc or options.link_libunwind or - target_util.osRequiresLibC(options.target); + const any_unwind_tables = options.config.any_unwind_tables; - const link_libunwind = options.link_libunwind or - (link_libcpp and target_util.libcNeedsLibUnwind(options.target)); - const unwind_tables = options.want_unwind_tables orelse - (link_libunwind or target_util.needUnwindTables(options.target)); - const link_eh_frame_hdr = options.link_eh_frame_hdr or unwind_tables; + const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables; const build_id = options.build_id orelse .none; - // Make a decision on whether to use LLD or our own linker. - const use_lld = options.use_lld orelse blk: { - if (options.target.isDarwin()) { - break :blk false; - } - - if (!build_options.have_llvm) - break :blk false; - - if (options.target.ofmt == .c) - break :blk false; - - if (options.want_lto) |lto| { - if (lto) { - break :blk true; - } - } - - // Our linker can't handle objects or most advanced options yet. - if (options.link_objects.len != 0 or - options.c_source_files.len != 0 or - options.frameworks.len != 0 or - options.system_lib_names.len != 0 or - options.link_libc or options.link_libcpp or - link_eh_frame_hdr or - options.link_emit_relocs or - options.output_mode == .Lib or - options.linker_script != null or options.version_script != null or - options.emit_implib != null or - build_id != .none or - options.symbol_wrap_set.count() > 0) - { - break :blk true; - } - - if (use_llvm) { - // If stage1 generates an object file, self-hosted linker is not - // yet sophisticated enough to handle that. - break :blk options.main_mod != null; - } - - break :blk false; - }; - - const lto = blk: { - if (options.want_lto) |want_lto| { - if (want_lto and !use_lld and !options.target.isDarwin()) - return error.LtoUnavailableWithoutLld; - break :blk want_lto; - } else if (!use_lld) { - // zig ld LTO support is tracked by - // https://github.com/ziglang/zig/issues/8680 - break :blk false; - } else if (options.c_source_files.len == 0) { - break :blk false; - } else if (options.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 :blk false; - } else switch (options.output_mode) { - .Lib, .Obj => break :blk false, - .Exe => switch (options.optimize_mode) { - .Debug => break :blk false, - .ReleaseSafe, .ReleaseFast, .ReleaseSmall => break :blk true, - }, - } - }; - - const must_dynamic_link = dl: { - if (target_util.cannotDynamicLink(options.target)) - break :dl false; - if (is_exe_or_dyn_lib and link_libc and - (options.target.isGnuLibC() or target_util.osRequiresLibC(options.target))) - { - break :dl true; - } - const any_dyn_libs: bool = x: { - if (options.system_lib_names.len != 0) - break :x true; - for (options.link_objects) |obj| { - switch (classifyFileExt(obj.path)) { - .shared_library => break :x true, - else => continue, - } - } - break :x false; - }; - if (any_dyn_libs) { - // When creating a executable that links to system libraries, - // we require dynamic linking, but we must not link static libraries - // or object files dynamically! - break :dl (options.output_mode == .Exe); - } - - break :dl false; - }; - const default_link_mode: std.builtin.LinkMode = blk: { - if (must_dynamic_link) { - break :blk .Dynamic; - } else if (is_exe_or_dyn_lib and link_libc and - options.is_native_abi and options.target.abi.isMusl()) - { - // If targeting the system's native ABI and the system's - // libc is musl, link dynamically by default. - break :blk .Dynamic; - } else { - break :blk .Static; - } - }; - const link_mode: std.builtin.LinkMode = if (options.link_mode) |lm| blk: { - if (lm == .Static and must_dynamic_link) { - return error.UnableToStaticLink; - } - break :blk lm; - } else default_link_mode; + const link_libc = options.config.link_libc; const dll_export_fns = options.dll_export_fns orelse (is_dyn_lib or options.rdynamic); const libc_dirs = try detectLibCIncludeDirs( arena, options.zig_lib_directory.path.?, - options.target, - options.is_native_abi, + options.root_mod.resolved_target.result, + options.root_mod.resolved_target.is_native_abi, link_libc, options.libc_installation, ); - const rc_dirs = try detectWin32ResourceIncludeDirs( - arena, - options, - ); + // The include directories used when preprocessing .rc files are separate from the + // target. Which include directories are used is determined by `options.rc_includes`. + // + // Note: It should be okay that the include directories used when compiling .rc + // files differ from the include directories used when compiling the main + // binary, since the .res format is not dependent on anything ABI-related. The + // only relevant differences would be things like `#define` constants being + // different in the MinGW headers vs the MSVC headers, but any such + // differences would likely be a MinGW bug. + const rc_dirs = b: { + // Set the includes to .none here when there are no rc files to compile + var includes = if (options.rc_source_files.len > 0) options.rc_includes else .none; + const target = options.root_mod.resolved_target.result; + if (!options.root_mod.resolved_target.is_native_os or target.os.tag != .windows) { + switch (includes) { + // MSVC can't be found when the host isn't Windows, so short-circuit. + .msvc => return error.WindowsSdkNotFound, + // Skip straight to gnu since we won't be able to detect + // MSVC on non-Windows hosts. + .any => includes = .gnu, + .none, .gnu => {}, + } + } + while (true) switch (includes) { + .any, .msvc => break :b detectLibCIncludeDirs( + arena, + options.zig_lib_directory.path.?, + .{ + .cpu = target.cpu, + .os = target.os, + .abi = .msvc, + .ofmt = target.ofmt, + }, + options.root_mod.resolved_target.is_native_abi, + // The .rc preprocessor will need to know the libc include dirs even if we + // are not linking libc, so force 'link_libc' to true + true, + options.libc_installation, + ) catch |err| { + if (includes == .any) { + // fall back to mingw + includes = .gnu; + continue; + } + return err; + }, + .gnu => break :b try detectLibCFromBuilding(arena, options.zig_lib_directory.path.?, .{ + .cpu = target.cpu, + .os = target.os, + .abi = .gnu, + .ofmt = target.ofmt, + }), + .none => break :b LibCDirs{ + .libc_include_dir_list = &[0][]u8{}, + .libc_installation = null, + .libc_framework_dir_list = &.{}, + .sysroot = null, + .darwin_sdk_layout = null, + }, + }; + }; const sysroot = options.sysroot orelse libc_dirs.sysroot; - const pie: bool = pie: { - if (is_dyn_lib) { - if (options.want_pie == true) return error.OutputModeForbidsPie; - break :pie false; - } - if (target_util.requiresPIE(options.target)) { - if (options.want_pie == false) return error.TargetRequiresPie; - break :pie true; - } - if (tsan) { - if (options.want_pie == false) return error.TsanRequiresPie; - break :pie true; - } - if (options.want_pie) |want_pie| { - break :pie want_pie; - } - break :pie false; - }; - - const must_pic: bool = b: { - if (target_util.requiresPIC(options.target, link_libc)) - break :b true; - break :b link_mode == .Dynamic; - }; - const pic = if (options.want_pic) |explicit| pic: { - if (!explicit) { - if (must_pic) { - return error.TargetRequiresPIC; - } - if (pie) { - return error.PIERequiresPIC; - } - } - break :pic explicit; - } else pie or must_pic; - - // Make a decision on whether to use Clang or Aro for translate-c and compiling C files. - const c_frontend: CFrontend = blk: { - if (options.use_clang) |want_clang| { - break :blk if (want_clang) .clang else .aro; - } - break :blk if (build_options.have_llvm) .clang else .aro; - }; - if (!build_options.have_llvm and c_frontend == .clang) { - return error.ZigCompilerNotBuiltWithLLVMExtensions; - } - - const is_safe_mode = switch (options.optimize_mode) { - .Debug, .ReleaseSafe => true, - .ReleaseFast, .ReleaseSmall => false, - }; - - const sanitize_c = options.want_sanitize_c orelse is_safe_mode; - - const stack_check: bool = options.want_stack_check orelse b: { - if (!target_util.supportsStackProbing(options.target)) break :b false; - break :b is_safe_mode; - }; - if (stack_check and !target_util.supportsStackProbing(options.target)) - return error.StackCheckUnsupportedByTarget; - - const stack_protector: u32 = sp: { - const zig_backend = zigBackend(options.target, use_llvm); - if (!target_util.supportsStackProtector(options.target, zig_backend)) { - if (options.want_stack_protector) |x| { - if (x > 0) return error.StackProtectorUnsupportedByTarget; - } - break :sp 0; - } - - // This logic is checking for linking libc because otherwise our start code - // which is trying to set up TLS (i.e. the fs/gs registers) but the stack - // protection code depends on fs/gs registers being already set up. - // If we were able to annotate start code, or perhaps the entire std lib, - // as being exempt from stack protection checks, we could change this logic - // to supporting stack protection even when not linking libc. - // TODO file issue about this - if (!link_libc) { - if (options.want_stack_protector) |x| { - if (x > 0) return error.StackProtectorUnavailableWithoutLibC; - } - break :sp 0; - } - - if (options.want_stack_protector) |x| break :sp x; - if (is_safe_mode) break :sp default_stack_protector_buffer_size; - break :sp 0; - }; - const include_compiler_rt = options.want_compiler_rt orelse (!options.skip_linker_dependencies and is_exe_or_dyn_lib); - const single_threaded = st: { - if (target_util.isSingleThreaded(options.target)) { - if (options.single_threaded == false) - return error.TargetRequiresSingleThreaded; - break :st true; - } - if (options.main_mod != null) { - const zig_backend = zigBackend(options.target, use_llvm); - if (!target_util.supportsThreads(options.target, zig_backend)) { - if (options.single_threaded == false) - return error.BackendRequiresSingleThreaded; - break :st true; - } - } - break :st options.single_threaded orelse false; - }; - - const llvm_cpu_features: ?[*:0]const u8 = if (use_llvm) blk: { - var buf = std.ArrayList(u8).init(arena); - for (options.target.cpu.arch.allFeaturesList(), 0..) |feature, index_usize| { - const index = @as(Target.Cpu.Feature.Set.Index, @intCast(index_usize)); - const is_enabled = options.target.cpu.features.isEnabled(index); - - if (feature.llvm_name) |llvm_name| { - const plus_or_minus = "-+"[@intFromBool(is_enabled)]; - try buf.ensureUnusedCapacity(2 + llvm_name.len); - buf.appendAssumeCapacity(plus_or_minus); - buf.appendSliceAssumeCapacity(llvm_name); - buf.appendSliceAssumeCapacity(","); - } - } - if (buf.items.len == 0) break :blk ""; - assert(mem.endsWith(u8, buf.items, ",")); - buf.items[buf.items.len - 1] = 0; - buf.shrinkAndFree(buf.items.len); - break :blk buf.items[0 .. buf.items.len - 1 :0].ptr; - } else null; + if (include_compiler_rt and output_mode == .Obj) { + // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");` + // injected into the object. + const compiler_rt_mod = try Package.Module.create(arena, .{ + .global_cache_directory = options.global_cache_directory, + .paths = .{ + .root = .{ + .root_dir = options.zig_lib_directory, + }, + .root_src_path = "compiler_rt.zig", + }, + .fully_qualified_name = "compiler_rt", + .cc_argv = &.{}, + .inherited = .{}, + .global = options.config, + .parent = options.root_mod, + .builtin_mod = options.root_mod.getBuiltinDependency(), + }); + try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod); + } if (options.verbose_llvm_cpu_features) { - if (llvm_cpu_features) |cf| print: { + if (options.root_mod.resolved_target.llvm_cpu_features) |cf| print: { + const target = options.root_mod.resolved_target.result; std.debug.getStderrMutex().lock(); defer std.debug.getStderrMutex().unlock(); const stderr = std.io.getStdErr().writer(); - nosuspend stderr.print("compilation: {s}\n", .{options.root_name}) catch break :print; - nosuspend stderr.print(" target: {s}\n", .{try options.target.zigTriple(arena)}) catch break :print; - nosuspend stderr.print(" cpu: {s}\n", .{options.target.cpu.model.name}) catch break :print; - nosuspend stderr.print(" features: {s}\n", .{cf}) catch {}; + nosuspend { + stderr.print("compilation: {s}\n", .{options.root_name}) catch break :print; + stderr.print(" target: {s}\n", .{try target.zigTriple(arena)}) catch break :print; + stderr.print(" cpu: {s}\n", .{target.cpu.model.name}) catch break :print; + stderr.print(" features: {s}\n", .{cf}) catch {}; + } } } - const strip = options.strip orelse !target_util.hasDebugInfo(options.target); - const valgrind: bool = b: { - if (!target_util.hasValgrindSupport(options.target)) break :b false; - if (options.want_valgrind) |explicit| break :b explicit; - if (strip) break :b false; - break :b options.optimize_mode == .Debug; - }; - if (!valgrind and options.want_valgrind == true) - return error.ValgrindUnsupportedOnTarget; - - const red_zone = options.want_red_zone orelse target_util.hasRedZone(options.target); - const omit_frame_pointer = options.omit_frame_pointer orelse (options.optimize_mode != .Debug); - const linker_optimization: u8 = options.linker_optimization orelse switch (options.optimize_mode) { + const linker_optimization: u8 = options.linker_optimization orelse switch (options.root_mod.optimize_mode) { .Debug => @as(u8, 0), else => @as(u8, 3), }; - const formatted_panics = options.formatted_panics orelse (options.optimize_mode == .Debug); + // TODO: https://github.com/ziglang/zig/issues/17969 + const formatted_panics = options.formatted_panics orelse (options.root_mod.optimize_mode == .Debug); const error_limit = options.error_limit orelse (std.math.maxInt(u16) - 1); @@ -1453,43 +1287,25 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // This is shared hasher state common to zig source and all C source files. cache.hash.addBytes(build_options.version); cache.hash.add(builtin.zig_backend); - cache.hash.add(options.optimize_mode); - cache.hash.add(options.target.cpu.arch); - cache.hash.addBytes(options.target.cpu.model.name); - cache.hash.add(options.target.cpu.features.ints); - cache.hash.add(options.target.os.tag); - cache.hash.add(options.target.os.getVersionRange()); - cache.hash.add(options.is_native_os); - cache.hash.add(options.target.abi); - cache.hash.add(options.target.ofmt); - cache.hash.add(pic); - cache.hash.add(pie); - cache.hash.add(lto); - cache.hash.add(unwind_tables); - cache.hash.add(tsan); - cache.hash.add(stack_check); - cache.hash.add(stack_protector); - cache.hash.add(red_zone); - cache.hash.add(omit_frame_pointer); - cache.hash.add(link_mode); + cache.hash.add(options.config.pie); + cache.hash.add(options.config.lto); + cache.hash.add(options.config.link_mode); cache.hash.add(options.function_sections); cache.hash.add(options.data_sections); cache.hash.add(options.no_builtin); - cache.hash.add(strip); cache.hash.add(link_libc); - cache.hash.add(link_libcpp); - cache.hash.add(link_libunwind); - cache.hash.add(options.output_mode); - cache.hash.add(options.machine_code_model); + cache.hash.add(options.config.link_libcpp); + cache.hash.add(options.config.link_libunwind); + cache.hash.add(output_mode); cache.hash.addOptional(options.dwarf_format); cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_bin); cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_implib); cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_docs); cache.hash.addBytes(options.root_name); - if (options.target.os.tag == .wasi) cache.hash.add(wasi_exec_model); + cache.hash.add(options.config.wasi_exec_model); // TODO audit this and make sure everything is in it - const module: ?*Module = if (options.main_mod) |main_mod| blk: { + const zcu: ?*Module = if (have_zcu) blk: { // Options that are specific to zig source files, that cannot be // modified between incremental updates. var hash = cache.hash; @@ -1502,13 +1318,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // do want to namespace different source file names because they are // likely different compilations and therefore this would be likely to // cause cache hits. - hash.addBytes(main_mod.root_src_path); - hash.addOptionalBytes(main_mod.root.root_dir.path); - hash.addBytes(main_mod.root.sub_path); - { - var seen_table = std.AutoHashMap(*Package.Module, void).init(arena); - try addModuleTableToCacheHash(&hash, &arena_allocator, main_mod.deps, &seen_table, .path_bytes); - } + try addModuleTableToCacheHash(gpa, arena, &hash, options.root_mod, .path_bytes); }, .whole => { // In this case, we postpone adding the input source file until @@ -1518,13 +1328,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { } // Synchronize with other matching comments: ZigOnlyHashStuff - hash.add(valgrind); - hash.add(single_threaded); hash.add(use_llvm); - hash.add(use_lib_llvm); + hash.add(options.config.use_lib_llvm); hash.add(dll_export_fns); - hash.add(options.is_test); - hash.add(options.test_evented_io); + hash.add(options.config.is_test); + hash.add(options.config.test_evented_io); hash.addOptionalBytes(options.test_filter); hash.addOptionalBytes(options.test_name_prefix); hash.add(options.skip_linker_dependencies); @@ -1565,85 +1373,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .path = try options.local_cache_directory.join(arena, &[_][]const u8{artifact_sub_dir}), }; - const builtin_mod = try Package.Module.create(arena, .{ - .root = .{ .root_dir = zig_cache_artifact_directory }, - .root_src_path = "builtin.zig", - .fully_qualified_name = "builtin", - }); - - // When you're testing std, the main module is std. In that case, - // we'll just set the std module to the main one, since avoiding - // the errors caused by duplicating it is more effort than it's - // worth. - const main_mod_is_std = m: { - const std_path = try std.fs.path.resolve(arena, &[_][]const u8{ - options.zig_lib_directory.path orelse ".", - "std", - "std.zig", - }); - const main_path = try std.fs.path.resolve(arena, &[_][]const u8{ - main_mod.root.root_dir.path orelse ".", - main_mod.root.sub_path, - main_mod.root_src_path, - }); - break :m mem.eql(u8, main_path, std_path); - }; - - const std_mod = if (main_mod_is_std) - main_mod - else - try Package.Module.create(arena, .{ - .root = .{ - .root_dir = options.zig_lib_directory, - .sub_path = "std", - }, - .root_src_path = "std.zig", - .fully_qualified_name = "std", - }); - - const root_mod = if (options.is_test) root_mod: { - const test_mod = if (options.test_runner_path) |test_runner| test_mod: { - const pkg = try Package.Module.create(arena, .{ - .root = .{ - .root_dir = Directory.cwd(), - .sub_path = std.fs.path.dirname(test_runner) orelse "", - }, - .root_src_path = std.fs.path.basename(test_runner), - .fully_qualified_name = "root", - }); - - pkg.deps = try main_mod.deps.clone(arena); - break :test_mod pkg; - } else try Package.Module.create(arena, .{ - .root = .{ - .root_dir = options.zig_lib_directory, - }, - .root_src_path = "test_runner.zig", - .fully_qualified_name = "root", - }); - - break :root_mod test_mod; - } else main_mod; - - const compiler_rt_mod = if (include_compiler_rt and options.output_mode == .Obj) compiler_rt_mod: { - break :compiler_rt_mod try Package.Module.create(arena, .{ - .root = .{ - .root_dir = options.zig_lib_directory, - }, - .root_src_path = "compiler_rt.zig", - .fully_qualified_name = "compiler_rt", - }); - } else null; - - { - try main_mod.deps.ensureUnusedCapacity(arena, 4); - main_mod.deps.putAssumeCapacity("builtin", builtin_mod); - main_mod.deps.putAssumeCapacity("root", root_mod); - main_mod.deps.putAssumeCapacity("std", std_mod); - if (compiler_rt_mod) |m| - main_mod.deps.putAssumeCapacity("compiler_rt", m); - } - // Pre-open the directory handles for cached ZIR code so that it does not need // to redundantly happen for each AstGen operation. const zir_sub_dir = "z"; @@ -1674,13 +1403,14 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // However we currently do not have serialization of such metadata, so for now // we set up an empty Module that does the entire compilation fresh. - const module = try arena.create(Module); - errdefer module.deinit(); - module.* = .{ + const zcu = try arena.create(Module); + errdefer zcu.deinit(); + zcu.* = .{ .gpa = gpa, .comp = comp, - .main_mod = main_mod, - .root_mod = root_mod, + .main_mod = options.main_mod orelse options.root_mod, + .root_mod = options.root_mod, + .std_mod = options.std_mod, .zig_cache_artifact_directory = zig_cache_artifact_directory, .global_zir_cache = global_zir_cache, .local_zir_cache = local_zir_cache, @@ -1688,31 +1418,24 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .tmp_hack_arena = std.heap.ArenaAllocator.init(gpa), .error_limit = error_limit, }; - try module.init(); + try zcu.init(); - break :blk module; + break :blk zcu; } else blk: { if (options.emit_h != null) return error.NoZigModuleForCHeader; break :blk null; }; - errdefer if (module) |zm| zm.deinit(); - - const error_return_tracing = !strip and switch (options.optimize_mode) { - .Debug, .ReleaseSafe => (!options.target.isWasm() or options.target.os.tag == .emscripten) and - !options.target.cpu.arch.isBpf() and (options.error_tracing orelse true), - .ReleaseFast => options.error_tracing orelse false, - .ReleaseSmall => false, - }; + errdefer if (zcu) |u| u.deinit(); // For resource management purposes. var owned_link_dir: ?std.fs.Dir = null; errdefer if (owned_link_dir) |*dir| dir.close(); - const bin_file_emit: ?link.Emit = blk: { + const bin_file_emit: ?Emit = blk: { const emit_bin = options.emit_bin orelse break :blk null; if (emit_bin.directory) |directory| { - break :blk link.Emit{ + break :blk Emit{ .directory = directory, .sub_path = emit_bin.basename, }; @@ -1725,9 +1448,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .incremental => {}, } - if (module) |zm| { - break :blk link.Emit{ - .directory = zm.zig_cache_artifact_directory, + if (zcu) |u| { + break :blk Emit{ + .directory = u.zig_cache_artifact_directory, .sub_path = emit_bin.basename, }; } @@ -1752,17 +1475,17 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .handle = artifact_dir, .path = try options.local_cache_directory.join(arena, &[_][]const u8{artifact_sub_dir}), }; - break :blk link.Emit{ + break :blk Emit{ .directory = link_artifact_directory, .sub_path = emit_bin.basename, }; }; - const implib_emit: ?link.Emit = blk: { + const implib_emit: ?Emit = blk: { const emit_implib = options.emit_implib orelse break :blk null; if (emit_implib.directory) |directory| { - break :blk link.Emit{ + break :blk Emit{ .directory = directory, .sub_path = emit_implib.basename, }; @@ -1776,13 +1499,13 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // Use the same directory as the bin. The CLI already emits an // error if -fno-emit-bin is combined with -femit-implib. - break :blk link.Emit{ + break :blk Emit{ .directory = bin_file_emit.?.directory, .sub_path = emit_implib.basename, }; }; - const docs_emit: ?link.Emit = blk: { + const docs_emit: ?Emit = blk: { const emit_docs = options.emit_docs orelse break :blk null; if (emit_docs.directory) |directory| { @@ -1805,7 +1528,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { }; break :blk .{ - .directory = module.?.zig_cache_artifact_directory, + .directory = zcu.?.zig_cache_artifact_directory, .sub_path = emit_docs.basename, }; }; @@ -1828,131 +1551,20 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { system_libs.putAssumeCapacity(lib_name, options.system_lib_infos[i]); } - const bin_file = try link.File.openPath(gpa, .{ - .emit = bin_file_emit, - .implib_emit = implib_emit, - .docs_emit = docs_emit, - .root_name = root_name, - .module = module, - .target = options.target, - .sysroot = sysroot, - .output_mode = options.output_mode, - .link_mode = link_mode, - .optimize_mode = options.optimize_mode, - .use_lld = use_lld, - .use_llvm = use_llvm, - .use_lib_llvm = use_lib_llvm, - .link_libc = link_libc, - .link_libcpp = link_libcpp, - .link_libunwind = link_libunwind, - .darwin_sdk_layout = libc_dirs.darwin_sdk_layout, - .objects = options.link_objects, - .frameworks = options.frameworks, - .framework_dirs = options.framework_dirs, - .system_libs = system_libs, - .wasi_emulated_libs = options.wasi_emulated_libs, - .lib_dirs = options.lib_dirs, - .rpath_list = options.rpath_list, - .symbol_wrap_set = options.symbol_wrap_set, - .strip = strip, - .is_native_os = options.is_native_os, - .is_native_abi = options.is_native_abi, - .function_sections = options.function_sections, - .data_sections = options.data_sections, - .no_builtin = options.no_builtin, - .allow_shlib_undefined = options.linker_allow_shlib_undefined, - .bind_global_refs_locally = options.linker_bind_global_refs_locally orelse false, - .compress_debug_sections = options.linker_compress_debug_sections orelse .none, - .module_definition_file = options.linker_module_definition_file, - .sort_section = options.linker_sort_section, - .import_memory = options.linker_import_memory orelse false, - .export_memory = options.linker_export_memory orelse !(options.linker_import_memory orelse false), - .import_symbols = options.linker_import_symbols, - .import_table = options.linker_import_table, - .export_table = options.linker_export_table, - .initial_memory = options.linker_initial_memory, - .max_memory = options.linker_max_memory, - .shared_memory = options.linker_shared_memory, - .global_base = options.linker_global_base, - .export_symbol_names = options.linker_export_symbol_names, - .print_gc_sections = options.linker_print_gc_sections, - .print_icf_sections = options.linker_print_icf_sections, - .print_map = options.linker_print_map, - .opt_bisect_limit = options.linker_opt_bisect_limit, - .z_nodelete = options.linker_z_nodelete, - .z_notext = options.linker_z_notext, - .z_defs = options.linker_z_defs, - .z_origin = options.linker_z_origin, - .z_nocopyreloc = options.linker_z_nocopyreloc, - .z_now = options.linker_z_now, - .z_relro = options.linker_z_relro, - .z_common_page_size = options.linker_z_common_page_size, - .z_max_page_size = options.linker_z_max_page_size, - .tsaware = options.linker_tsaware, - .nxcompat = options.linker_nxcompat, - .dynamicbase = options.linker_dynamicbase, - .linker_optimization = linker_optimization, - .major_subsystem_version = options.major_subsystem_version, - .minor_subsystem_version = options.minor_subsystem_version, - .entry = options.entry, - .stack_size_override = options.stack_size_override, - .image_base_override = options.image_base_override, - .include_compiler_rt = include_compiler_rt, - .linker_script = options.linker_script, - .version_script = options.version_script, - .gc_sections = options.linker_gc_sections, - .eh_frame_hdr = link_eh_frame_hdr, - .emit_relocs = options.link_emit_relocs, - .rdynamic = options.rdynamic, - .soname = options.soname, - .version = options.version, - .compatibility_version = options.compatibility_version, - .libc_installation = libc_dirs.libc_installation, - .pic = pic, - .pie = pie, - .lto = lto, - .valgrind = valgrind, - .tsan = tsan, - .stack_check = stack_check, - .stack_protector = stack_protector, - .red_zone = red_zone, - .omit_frame_pointer = omit_frame_pointer, - .single_threaded = single_threaded, - .verbose_link = options.verbose_link, - .machine_code_model = options.machine_code_model, - .dll_export_fns = dll_export_fns, - .error_return_tracing = error_return_tracing, - .llvm_cpu_features = llvm_cpu_features, - .skip_linker_dependencies = options.skip_linker_dependencies, - .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os, - .build_id = build_id, - .cache_mode = cache_mode, - .disable_lld_caching = options.disable_lld_caching or cache_mode == .whole, - .subsystem = options.subsystem, - .is_test = options.is_test, - .dwarf_format = options.dwarf_format, - .wasi_exec_model = wasi_exec_model, - .hash_style = options.hash_style, - .enable_link_snapshots = options.enable_link_snapshots, - .install_name = options.install_name, - .entitlements = options.entitlements, - .pagezero_size = options.pagezero_size, - .headerpad_size = options.headerpad_size, - .headerpad_max_install_names = options.headerpad_max_install_names, - .dead_strip_dylibs = options.dead_strip_dylibs, - .force_undefined_symbols = options.force_undefined_symbols, - .pdb_source_path = options.pdb_source_path, - .pdb_out_path = options.pdb_out_path, - .want_structured_cfg = options.want_structured_cfg, - }); - errdefer bin_file.destroy(); + const each_lib_rpath = options.each_lib_rpath orelse + options.root_mod.resolved_target.is_native_os; + comp.* = .{ .gpa = gpa, .arena = arena_allocator, + .module = zcu, + .root_mod = options.root_mod, + .config = options.config, + .bin_file = null, + .cache_mode = cache_mode, .zig_lib_directory = options.zig_lib_directory, .local_cache_directory = options.local_cache_directory, .global_cache_directory = options.global_cache_directory, - .bin_file = bin_file, .whole_bin_sub_path = whole_bin_sub_path, .whole_implib_sub_path = whole_implib_sub_path, .whole_docs_sub_path = whole_docs_sub_path, @@ -1966,8 +1578,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .astgen_work_queue = std.fifo.LinearFifo(*Module.File, .Dynamic).init(gpa), .embed_file_work_queue = std.fifo.LinearFifo(*Module.EmbedFile, .Dynamic).init(gpa), .keep_source_files_loaded = options.keep_source_files_loaded, - .c_frontend = c_frontend, - .clang_argv = options.clang_argv, .c_source_files = options.c_source_files, .rc_source_files = options.rc_source_files, .cache_parent = cache, @@ -1975,7 +1585,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .libc_include_dir_list = libc_dirs.libc_include_dir_list, .libc_framework_dir_list = libc_dirs.libc_framework_dir_list, .rc_include_dir_list = rc_dirs.libc_include_dir_list, - .sanitize_c = sanitize_c, .thread_pool = options.thread_pool, .clang_passthrough_mode = options.clang_passthrough_mode, .clang_preprocessor_mode = options.clang_preprocessor_mode, @@ -1994,22 +1603,110 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .formatted_panics = formatted_panics, .time_report = options.time_report, .stack_report = options.stack_report, - .unwind_tables = unwind_tables, .test_filter = options.test_filter, .test_name_prefix = options.test_name_prefix, - .test_evented_io = options.test_evented_io, .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs, .debug_compile_errors = options.debug_compile_errors, .libcxx_abi_version = options.libcxx_abi_version, + .implib_emit = implib_emit, + .docs_emit = docs_emit, + .root_name = root_name, + .sysroot = sysroot, + .system_libs = system_libs, + .version = options.version, + .libc_installation = libc_dirs.libc_installation, + .include_compiler_rt = include_compiler_rt, + .objects = options.link_objects, }; + + if (bin_file_emit) |emit| { + comp.bin_file = try link.File.open(arena, .{ + .comp = comp, + .emit = emit, + .optimization = linker_optimization, + .linker_script = options.linker_script, + .z_nodelete = options.linker_z_nodelete, + .z_notext = options.linker_z_notext, + .z_defs = options.linker_z_defs, + .z_origin = options.linker_z_origin, + .z_nocopyreloc = options.linker_z_nocopyreloc, + .z_now = options.linker_z_now, + .z_relro = options.linker_z_relro, + .z_common_page_size = options.linker_z_common_page_size, + .z_max_page_size = options.linker_z_max_page_size, + .darwin_sdk_layout = libc_dirs.darwin_sdk_layout, + .frameworks = options.frameworks, + .framework_dirs = options.framework_dirs, + .wasi_emulated_libs = options.wasi_emulated_libs, + .lib_dirs = options.lib_dirs, + .rpath_list = options.rpath_list, + .symbol_wrap_set = options.symbol_wrap_set, + .function_sections = options.function_sections, + .data_sections = options.data_sections, + .no_builtin = options.no_builtin, + .allow_shlib_undefined = options.linker_allow_shlib_undefined, + .bind_global_refs_locally = options.linker_bind_global_refs_locally orelse false, + .compress_debug_sections = options.linker_compress_debug_sections orelse .none, + .module_definition_file = options.linker_module_definition_file, + .sort_section = options.linker_sort_section, + .import_symbols = options.linker_import_symbols, + .import_table = options.linker_import_table, + .export_table = options.linker_export_table, + .initial_memory = options.linker_initial_memory, + .max_memory = options.linker_max_memory, + .global_base = options.linker_global_base, + .export_symbol_names = options.linker_export_symbol_names, + .print_gc_sections = options.linker_print_gc_sections, + .print_icf_sections = options.linker_print_icf_sections, + .print_map = options.linker_print_map, + .opt_bisect_limit = options.linker_opt_bisect_limit, + .tsaware = options.linker_tsaware, + .nxcompat = options.linker_nxcompat, + .dynamicbase = options.linker_dynamicbase, + .major_subsystem_version = options.major_subsystem_version, + .minor_subsystem_version = options.minor_subsystem_version, + .stack_size_override = options.stack_size_override, + .image_base_override = options.image_base_override, + .version_script = options.version_script, + .gc_sections = options.linker_gc_sections, + .eh_frame_hdr = link_eh_frame_hdr, + .emit_relocs = options.link_emit_relocs, + .rdynamic = options.rdynamic, + .soname = options.soname, + .compatibility_version = options.compatibility_version, + .verbose_link = options.verbose_link, + .dll_export_fns = dll_export_fns, + .skip_linker_dependencies = options.skip_linker_dependencies, + .parent_compilation_link_libc = options.parent_compilation_link_libc, + .each_lib_rpath = each_lib_rpath, + .build_id = build_id, + .disable_lld_caching = options.disable_lld_caching or cache_mode == .whole, + .subsystem = options.subsystem, + .dwarf_format = options.dwarf_format, + .hash_style = options.hash_style, + .enable_link_snapshots = options.enable_link_snapshots, + .install_name = options.install_name, + .entitlements = options.entitlements, + .pagezero_size = options.pagezero_size, + .headerpad_size = options.headerpad_size, + .headerpad_max_install_names = options.headerpad_max_install_names, + .dead_strip_dylibs = options.dead_strip_dylibs, + .force_undefined_symbols = options.force_undefined_symbols, + .pdb_source_path = options.pdb_source_path, + .pdb_out_path = options.pdb_out_path, + .want_structured_cfg = options.want_structured_cfg, + .entry_addr = null, // CLI does not expose this option (yet?) + }); + } + break :comp comp; }; errdefer comp.destroy(); - const target = comp.getTarget(); + const target = options.root_mod.resolved_target.result; - const capable_of_building_compiler_rt = canBuildLibCompilerRt(target, comp.bin_file.options.use_llvm); - const capable_of_building_zig_libc = canBuildZigLibC(target, comp.bin_file.options.use_llvm); + const capable_of_building_compiler_rt = canBuildLibCompilerRt(target, options.config.use_llvm); + const capable_of_building_zig_libc = canBuildZigLibC(target, options.config.use_llvm); // Add a `CObject` for each `c_source_files`. try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len); @@ -2109,7 +1806,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { }); } comp.work_queue.writeAssumeCapacity(&[_]Job{ - .{ .wasi_libc_crt_file = wasi_libc.execModelCrtFile(wasi_exec_model) }, + .{ .wasi_libc_crt_file = wasi_libc.execModelCrtFile(options.config.wasi_exec_model) }, .{ .wasi_libc_crt_file = .libc_a }, }); } @@ -2171,7 +1868,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { if (is_exe_or_dyn_lib) { log.debug("queuing a job to build compiler_rt_lib", .{}); comp.job_queued_compiler_rt_lib = true; - } else if (options.output_mode != .Obj) { + } else if (output_mode != .Obj) { log.debug("queuing a job to build compiler_rt_obj", .{}); // In this case we are making a static library, so we ask // for a compiler-rt object to put in it. @@ -2283,7 +1980,7 @@ pub fn clearMiscFailures(comp: *Compilation) void { } pub fn getTarget(self: Compilation) Target { - return self.bin_file.options.target; + return self.root_mod.resolved_target.result; } fn restorePrevZigCacheArtifactDirectory(comp: *Compilation, directory: *Directory) void { @@ -2436,7 +2133,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void // Make sure std.zig is inside the import_table. We unconditionally need // it for start.zig. - const std_mod = module.main_mod.deps.get("std").?; + const std_mod = module.std_mod; _ = try module.importPkg(std_mod); // Normally we rely on importing std to in turn import the root source file @@ -2449,7 +2146,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void _ = try module.importPkg(module.main_mod); } - if (module.main_mod.deps.get("compiler_rt")) |compiler_rt_mod| { + if (module.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { _ = try module.importPkg(compiler_rt_mod); } @@ -2474,7 +2171,7 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void try comp.work_queue.writeItem(.{ .analyze_mod = module.main_mod }); } - if (module.main_mod.deps.get("compiler_rt")) |compiler_rt_mod| { + if (module.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { try comp.work_queue.writeItem(.{ .analyze_mod = compiler_rt_mod }); } } @@ -2699,16 +2396,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes if (comp.bin_file.options.module) |mod| { const main_zig_file = try mod.main_mod.root.joinString(arena, mod.main_mod.root_src_path); _ = try man.addFile(main_zig_file, null); - { - var seen_table = std.AutoHashMap(*Package.Module, void).init(arena); - - // Skip builtin.zig; it is useless as an input, and we don't want to have to - // write it before checking for a cache hit. - const builtin_mod = mod.main_mod.deps.get("builtin").?; - try seen_table.put(builtin_mod, {}); - - try addModuleTableToCacheHash(&man.hash, &arena_allocator, mod.main_mod.deps, &seen_table, .{ .files = man }); - } + try addModuleTableToCacheHash(gpa, arena, &man.hash, mod.main_mod, .{ .files = man }); // Synchronize with other matching comments: ZigOnlyHashStuff man.hash.add(comp.bin_file.options.valgrind); @@ -2762,8 +2450,6 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc); - man.hash.addListOfBytes(comp.clang_argv); - man.hash.addOptional(comp.bin_file.options.stack_size_override); man.hash.addOptional(comp.bin_file.options.image_base_override); man.hash.addOptional(comp.bin_file.options.gc_sections); @@ -3341,7 +3027,7 @@ pub const ErrorNoteHashContext = struct { const eb = ctx.eb.tmpBundle(); const msg_a = eb.nullTerminatedString(a.msg); const msg_b = eb.nullTerminatedString(b.msg); - if (!std.mem.eql(u8, msg_a, msg_b)) return false; + if (!mem.eql(u8, msg_a, msg_b)) return false; if (a.src_loc == .none and b.src_loc == .none) return true; if (a.src_loc == .none or b.src_loc == .none) return false; @@ -3351,7 +3037,7 @@ pub const ErrorNoteHashContext = struct { const src_path_a = eb.nullTerminatedString(src_a.src_path); const src_path_b = eb.nullTerminatedString(src_b.src_path); - return std.mem.eql(u8, src_path_a, src_path_b) and + return mem.eql(u8, src_path_a, src_path_b) and src_a.line == src_b.line and src_a.column == src_b.column and src_a.span_main == src_b.span_main; @@ -4149,7 +3835,7 @@ pub const CImportResult = struct { cache_hit: bool, errors: std.zig.ErrorBundle, - pub fn deinit(result: *CImportResult, gpa: std.mem.Allocator) void { + pub fn deinit(result: *CImportResult, gpa: mem.Allocator) void { result.errors.deinit(gpa); } }; @@ -5321,9 +5007,9 @@ pub fn addCCArgs( argv.appendAssumeCapacity(arg); } } - const mcmodel = comp.bin_file.options.machine_code_model; - if (mcmodel != .default) { - try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={s}", .{@tagName(mcmodel)})); + const code_model = comp.bin_file.options.machine_code_model; + if (code_model != .default) { + try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={s}", .{@tagName(code_model)})); } switch (target.os.tag) { @@ -5618,68 +5304,6 @@ fn failCObjWithOwnedDiagBundle( return error.AnalysisFail; } -/// The include directories used when preprocessing .rc files are separate from the -/// target. Which include directories are used is determined by `options.rc_includes`. -/// -/// Note: It should be okay that the include directories used when compiling .rc -/// files differ from the include directories used when compiling the main -/// binary, since the .res format is not dependent on anything ABI-related. The -/// only relevant differences would be things like `#define` constants being -/// different in the MinGW headers vs the MSVC headers, but any such -/// differences would likely be a MinGW bug. -fn detectWin32ResourceIncludeDirs(arena: Allocator, options: InitOptions) !LibCDirs { - // Set the includes to .none here when there are no rc files to compile - var includes = if (options.rc_source_files.len > 0) options.rc_includes else .none; - if (builtin.target.os.tag != .windows) { - switch (includes) { - // MSVC can't be found when the host isn't Windows, so short-circuit. - .msvc => return error.WindowsSdkNotFound, - // Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts. - .any => includes = .gnu, - .none, .gnu => {}, - } - } - while (true) { - switch (includes) { - .any, .msvc => return detectLibCIncludeDirs( - arena, - options.zig_lib_directory.path.?, - .{ - .cpu = options.target.cpu, - .os = options.target.os, - .abi = .msvc, - .ofmt = options.target.ofmt, - }, - options.is_native_abi, - // The .rc preprocessor will need to know the libc include dirs even if we - // are not linking libc, so force 'link_libc' to true - true, - options.libc_installation, - ) catch |err| { - if (includes == .any) { - // fall back to mingw - includes = .gnu; - continue; - } - return err; - }, - .gnu => return detectLibCFromBuilding(arena, options.zig_lib_directory.path.?, .{ - .cpu = options.target.cpu, - .os = options.target.os, - .abi = .gnu, - .ofmt = options.target.ofmt, - }), - .none => return LibCDirs{ - .libc_include_dir_list = &[0][]u8{}, - .libc_installation = null, - .libc_framework_dir_list = &.{}, - .sysroot = null, - .darwin_sdk_layout = null, - }, - } - } -} - fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) SemaError { @setCold(true); var bundle: ErrorBundle.Wip = undefined; @@ -6061,7 +5685,7 @@ const LibCDirs = struct { libc_installation: ?*const LibCInstallation, libc_framework_dir_list: []const []const u8, sysroot: ?[]const u8, - darwin_sdk_layout: ?link.DarwinSdkLayout, + darwin_sdk_layout: ?link.File.MachO.SdkLayout, }; fn getZigShippedLibCIncludeDirsDarwin(arena: Allocator, zig_lib_dir: []const u8) !LibCDirs { @@ -6374,7 +5998,7 @@ fn parseLldStderr(comp: *Compilation, comptime prefix: []const u8, stderr: []con err.context_lines = try context_lines.toOwnedSlice(); } - var split = std.mem.splitSequence(u8, line, "error: "); + var split = mem.splitSequence(u8, line, "error: "); _ = split.first(); const duped_msg = try std.fmt.allocPrint(comp.gpa, "{s}: {s}", .{ prefix, split.rest() }); @@ -6427,7 +6051,7 @@ fn canBuildLibCompilerRt(target: std.Target, use_llvm: bool) bool { .spirv32, .spirv64 => return false, else => {}, } - return switch (zigBackend(target, use_llvm)) { + return switch (target_util.zigBackend(target, use_llvm)) { .stage2_llvm => true, .stage2_x86_64 => if (target.ofmt == .elf) true else build_options.have_llvm, else => build_options.have_llvm, @@ -6445,7 +6069,7 @@ fn canBuildZigLibC(target: std.Target, use_llvm: bool) bool { .spirv32, .spirv64 => return false, else => {}, } - return switch (zigBackend(target, use_llvm)) { + return switch (target_util.zigBackend(target, use_llvm)) { .stage2_llvm => true, .stage2_x86_64 => if (target.ofmt == .elf) true else build_options.have_llvm, else => build_options.have_llvm, @@ -6454,236 +6078,7 @@ fn canBuildZigLibC(target: std.Target, use_llvm: bool) bool { pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend { const target = comp.bin_file.options.target; - return zigBackend(target, comp.bin_file.options.use_llvm); -} - -fn zigBackend(target: std.Target, use_llvm: bool) std.builtin.CompilerBackend { - if (use_llvm) return .stage2_llvm; - if (target.ofmt == .c) return .stage2_c; - return switch (target.cpu.arch) { - .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, - .arm, .armeb, .thumb, .thumbeb => .stage2_arm, - .x86_64 => .stage2_x86_64, - .x86 => .stage2_x86, - .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, - .riscv64 => .stage2_riscv64, - .sparc64 => .stage2_sparc64, - .spirv64 => .stage2_spirv64, - else => .other, - }; -} - -pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 { - const tracy_trace = trace(@src()); - defer tracy_trace.end(); - - var buffer = std.ArrayList(u8).init(allocator); - defer buffer.deinit(); - - const target = comp.getTarget(); - const generic_arch_name = target.cpu.arch.genericName(); - const zig_backend = comp.getZigBackend(); - - @setEvalBranchQuota(4000); - try buffer.writer().print( - \\const std = @import("std"); - \\/// Zig version. When writing code that supports multiple versions of Zig, prefer - \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks. - \\pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable; - \\pub const zig_version_string = "{s}"; - \\pub const zig_backend = std.builtin.CompilerBackend.{}; - \\ - \\pub const output_mode = std.builtin.OutputMode.{}; - \\pub const link_mode = std.builtin.LinkMode.{}; - \\pub const is_test = {}; - \\pub const single_threaded = {}; - \\pub const abi = std.Target.Abi.{}; - \\pub const cpu: std.Target.Cpu = .{{ - \\ .arch = .{}, - \\ .model = &std.Target.{}.cpu.{}, - \\ .features = std.Target.{}.featureSet(&[_]std.Target.{}.Feature{{ - \\ - , .{ - build_options.version, - std.zig.fmtId(@tagName(zig_backend)), - std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), - std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), - comp.bin_file.options.is_test, - comp.bin_file.options.single_threaded, - std.zig.fmtId(@tagName(target.abi)), - std.zig.fmtId(@tagName(target.cpu.arch)), - std.zig.fmtId(generic_arch_name), - std.zig.fmtId(target.cpu.model.name), - std.zig.fmtId(generic_arch_name), - std.zig.fmtId(generic_arch_name), - }); - - for (target.cpu.arch.allFeaturesList(), 0..) |feature, index_usize| { - const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize)); - const is_enabled = target.cpu.features.isEnabled(index); - if (is_enabled) { - try buffer.writer().print(" .{},\n", .{std.zig.fmtId(feature.name)}); - } - } - try buffer.writer().print( - \\ }}), - \\}}; - \\pub const os = std.Target.Os{{ - \\ .tag = .{}, - \\ .version_range = .{{ - , - .{std.zig.fmtId(@tagName(target.os.tag))}, - ); - - switch (target.os.getVersionRange()) { - .none => try buffer.appendSlice(" .none = {} },\n"), - .semver => |semver| try buffer.writer().print( - \\ .semver = .{{ - \\ .min = .{{ - \\ .major = {}, - \\ .minor = {}, - \\ .patch = {}, - \\ }}, - \\ .max = .{{ - \\ .major = {}, - \\ .minor = {}, - \\ .patch = {}, - \\ }}, - \\ }}}}, - \\ - , .{ - semver.min.major, - semver.min.minor, - semver.min.patch, - - semver.max.major, - semver.max.minor, - semver.max.patch, - }), - .linux => |linux| try buffer.writer().print( - \\ .linux = .{{ - \\ .range = .{{ - \\ .min = .{{ - \\ .major = {}, - \\ .minor = {}, - \\ .patch = {}, - \\ }}, - \\ .max = .{{ - \\ .major = {}, - \\ .minor = {}, - \\ .patch = {}, - \\ }}, - \\ }}, - \\ .glibc = .{{ - \\ .major = {}, - \\ .minor = {}, - \\ .patch = {}, - \\ }}, - \\ }}}}, - \\ - , .{ - linux.range.min.major, - linux.range.min.minor, - linux.range.min.patch, - - linux.range.max.major, - linux.range.max.minor, - linux.range.max.patch, - - linux.glibc.major, - linux.glibc.minor, - linux.glibc.patch, - }), - .windows => |windows| try buffer.writer().print( - \\ .windows = .{{ - \\ .min = {s}, - \\ .max = {s}, - \\ }}}}, - \\ - , - .{ windows.min, windows.max }, - ), - } - try buffer.appendSlice( - \\}; - \\pub const target: std.Target = .{ - \\ .cpu = cpu, - \\ .os = os, - \\ .abi = abi, - \\ .ofmt = object_format, - \\ - ); - - if (target.dynamic_linker.get()) |dl| { - try buffer.writer().print( - \\ .dynamic_linker = std.Target.DynamicLinker.init("{s}"), - \\}}; - \\ - , .{dl}); - } else { - try buffer.appendSlice( - \\ .dynamic_linker = std.Target.DynamicLinker.none, - \\}; - \\ - ); - } - - try buffer.writer().print( - \\pub const object_format = std.Target.ObjectFormat.{}; - \\pub const mode = std.builtin.OptimizeMode.{}; - \\pub const link_libc = {}; - \\pub const link_libcpp = {}; - \\pub const have_error_return_tracing = {}; - \\pub const valgrind_support = {}; - \\pub const sanitize_thread = {}; - \\pub const position_independent_code = {}; - \\pub const position_independent_executable = {}; - \\pub const strip_debug_info = {}; - \\pub const code_model = std.builtin.CodeModel.{}; - \\pub const omit_frame_pointer = {}; - \\ - , .{ - std.zig.fmtId(@tagName(target.ofmt)), - std.zig.fmtId(@tagName(comp.bin_file.options.optimize_mode)), - comp.bin_file.options.link_libc, - comp.bin_file.options.link_libcpp, - comp.bin_file.options.error_return_tracing, - comp.bin_file.options.valgrind, - comp.bin_file.options.tsan, - comp.bin_file.options.pic, - comp.bin_file.options.pie, - comp.bin_file.options.strip, - std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)), - comp.bin_file.options.omit_frame_pointer, - }); - - if (target.os.tag == .wasi) { - const wasi_exec_model_fmt = std.zig.fmtId(@tagName(comp.bin_file.options.wasi_exec_model)); - try buffer.writer().print( - \\pub const wasi_exec_model = std.builtin.WasiExecModel.{}; - \\ - , .{wasi_exec_model_fmt}); - } - - if (comp.bin_file.options.is_test) { - try buffer.appendSlice( - \\pub var test_functions: []const std.builtin.TestFn = undefined; // overwritten later - \\ - ); - if (comp.test_evented_io) { - try buffer.appendSlice( - \\pub const test_io_mode = .evented; - \\ - ); - } else { - try buffer.appendSlice( - \\pub const test_io_mode = .blocking; - \\ - ); - } - } - - return buffer.toOwnedSliceSentinel(0); + return target_util.zigBackend(target, comp.bin_file.options.use_llvm); } pub fn updateSubCompilation( @@ -6730,21 +6125,46 @@ fn buildOutputFromZig( const tracy_trace = trace(@src()); defer tracy_trace.end(); + var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + assert(output_mode != .Exe); - var main_mod: Package.Module = .{ - .root = .{ .root_dir = comp.zig_lib_directory }, - .root_src_path = src_basename, + const config = try Config.resolve(.{ + .output_mode = output_mode, + .resolved_target = comp.root_mod.resolved_target, + .is_test = false, + .have_zcu = true, + .emit_bin = true, + .root_optimize_mode = comp.compilerRtOptMode(), + }); + + const root_mod = Package.Module.create(.{ + .paths = .{ + .root = .{ .root_dir = comp.zig_lib_directory }, + .root_src_path = src_basename, + }, .fully_qualified_name = "root", - }; + .inherited = .{ + .strip = comp.compilerRtStrip(), + .stack_check = false, + .stack_protector = 0, + .red_zone = comp.root_mod.red_zone, + .omit_frame_pointer = comp.root_mod.omit_frame_pointer, + .unwind_tables = comp.bin_file.options.eh_frame_hdr, + .pic = comp.root_mod.pic, + }, + .global = config, + .cc_argv = &.{}, + }); const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len]; const target = comp.getTarget(); - const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{ + const bin_basename = try std.zig.binNameAlloc(arena, .{ .root_name = root_name, .target = target, .output_mode = output_mode, }); - defer comp.gpa.free(bin_basename); const emit_bin = Compilation.EmitLoc{ .directory = null, // Put it in the cache directory. @@ -6754,33 +6174,18 @@ fn buildOutputFromZig( .global_cache_directory = comp.global_cache_directory, .local_cache_directory = comp.global_cache_directory, .zig_lib_directory = comp.zig_lib_directory, + .resolved = config, .cache_mode = .whole, - .target = target, .root_name = root_name, - .main_mod = &main_mod, - .output_mode = output_mode, + .root_mod = root_mod, .thread_pool = comp.thread_pool, .libc_installation = comp.bin_file.options.libc_installation, .emit_bin = emit_bin, - .optimize_mode = comp.compilerRtOptMode(), .link_mode = .Static, .function_sections = true, .data_sections = true, .no_builtin = true, - .want_sanitize_c = false, - .want_stack_check = false, - .want_stack_protector = 0, - .want_red_zone = comp.bin_file.options.red_zone, - .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, - .want_valgrind = false, - .want_tsan = false, - .want_unwind_tables = comp.bin_file.options.eh_frame_hdr, - .want_pic = comp.bin_file.options.pic, - .want_pie = null, .emit_h = null, - .strip = comp.compilerRtStrip(), - .is_native_os = comp.bin_file.options.is_native_os, - .is_native_abi = comp.bin_file.options.is_native_abi, .self_exe_path = comp.self_exe_path, .verbose_cc = comp.verbose_cc, .verbose_link = comp.bin_file.options.verbose_link, @@ -6815,7 +6220,7 @@ pub fn build_crt_file( output_mode: std.builtin.OutputMode, misc_task_tag: MiscTask, prog_node: *std.Progress.Node, - c_source_files: []const Compilation.CSourceFile, + c_source_files: []const CSourceFile, ) !void { const tracy_trace = trace(@src()); defer tracy_trace.end(); diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig new file mode 100644 index 0000000000000000000000000000000000000000..0b9c70fc4244c8ace431f5f2e8602a271a228660 --- /dev/null +++ b/src/Compilation/Config.zig @@ -0,0 +1,382 @@ +//! 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, +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, + +pub const CFrontend = enum { clang, aro }; + +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, + 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, + c_source_files_len: usize = 0, + 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, +}; + +pub fn resolve(options: Options) !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; + }; + + const entry: ?[]const u8 = switch (options.entry) { + .disabled => null, + .default => b: { + if (options.output_mode != .Exe) 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; + + // *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; + 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 (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 (!use_lib_llvm and use_llvm and options.emit_bin) { + // Explicit request to use LLVM to produce an object file, but without + // using LLVM libraries. Impossible. + return error.EmittingBinaryRequiresLlvmLibrary; + } + + // Make a decision on whether to use LLD or our own linker. + const use_lld = b: { + if (target.isDarwin()) { + if (options.use_lld == true) return error.LldIncompatibleOs; + break :b false; + } + + if (!build_options.have_llvm) { + if (options.use_lld == true) return error.LldUnavailable; + break :b false; + } + + if (target.ofmt == .c) { + if (options.use_lld == true) return error.LldIncompatibleObjectFormat; + 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.c_source_files_len == 0) 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 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 false; + 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; + }; + + 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, + .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, + }; +} + +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"); diff --git a/src/Module.zig b/src/Module.zig index 6e5609f63c02d5909c80d44f0a022ce45aa24e33..5ec949293e55a98b7a350fb18c4dd419697206b0 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -59,6 +59,7 @@ root_mod: *Package.Module, /// Normally, `main_mod` and `root_mod` are the same. The exception is `zig test`, in which /// `root_mod` is the test runner, and `main_mod` is the user's source file which has the tests. main_mod: *Package.Module, +std_mod: *Package.Module, sema_prog_node: std.Progress.Node = undefined, /// Used by AstGen worker to load and store ZIR cache. @@ -3599,7 +3600,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool { // TODO: figure out how this works under incremental changes to builtin.zig! const builtin_type_target_index: InternPool.Index = blk: { - const std_mod = mod.main_mod.deps.get("std").?; + const std_mod = mod.std_mod; if (decl.getFileScope(mod).mod != std_mod) break :blk .none; // We're in the std module. const std_file = (try mod.importPkg(std_mod)).file; @@ -3924,10 +3925,7 @@ pub fn importFile( import_string: []const u8, ) !ImportFileResult { if (std.mem.eql(u8, import_string, "std")) { - return mod.importPkg(mod.main_mod.deps.get("std").?); - } - if (std.mem.eql(u8, import_string, "builtin")) { - return mod.importPkg(mod.main_mod.deps.get("builtin").?); + return mod.importPkg(mod.std_mod); } if (std.mem.eql(u8, import_string, "root")) { return mod.importPkg(mod.root_mod); diff --git a/src/Package/Module.zig b/src/Package/Module.zig index 0c8d40bffa65fd406282769d18044e6024515a45..94431856acf0aa6d8e6bb7d81910ef14cd0f1ee1 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -1,6 +1,6 @@ //! Corresponds to something that Zig source code can `@import`. -//! Not to be confused with src/Module.zig which should be renamed -//! to something else. https://github.com/ziglang/zig/issues/14307 +//! Not to be confused with src/Module.zig which will be renamed +//! to Zcu. https://github.com/ziglang/zig/issues/14307 /// Only files inside this directory can be imported. root: Package.Path, @@ -14,6 +14,26 @@ fully_qualified_name: []const u8, /// responsible for detecting these names and using the correct package. deps: Deps = .{}, +resolved_target: ResolvedTarget, +optimize_mode: std.builtin.OptimizeMode, +code_model: std.builtin.CodeModel, +single_threaded: bool, +error_tracing: bool, +valgrind: bool, +pic: bool, +strip: bool, +omit_frame_pointer: bool, +stack_check: bool, +stack_protector: u32, +red_zone: bool, +sanitize_c: bool, +sanitize_thread: bool, +unwind_tables: bool, +cc_argv: []const []const u8, + +/// The contents of `@import("builtin")` for this module. +generated_builtin_source: []const u8, + pub const Deps = std.StringArrayHashMapUnmanaged(*Module); pub const Tree = struct { @@ -21,10 +41,382 @@ pub const Tree = struct { build_module_table: std.AutoArrayHashMapUnmanaged(MultiHashHexDigest, *Module), }; -pub fn create(allocator: Allocator, m: Module) Allocator.Error!*Module { - const new = try allocator.create(Module); - new.* = m; - return new; +pub const CreateOptions = struct { + /// Where to store builtin.zig. The global cache directory is used because + /// it is a pure function based on CLI flags. + global_cache_directory: Cache.Directory, + paths: Paths, + fully_qualified_name: []const u8, + + cc_argv: []const []const u8, + inherited: Inherited, + global: Compilation.Config, + /// If this is null then `resolved_target` must be non-null. + parent: ?*Package.Module, + + builtin_mod: ?*Package.Module, + + pub const Paths = struct { + root: Package.Path, + /// Relative to `root`. May contain path separators. + root_src_path: []const u8, + }; + + pub const Inherited = struct { + /// If this is null then `parent` must be non-null. + resolved_target: ?ResolvedTarget = null, + optimize_mode: ?std.builtin.OptimizeMode = null, + code_model: ?std.builtin.CodeModel = null, + single_threaded: ?bool = null, + error_tracing: ?bool = null, + valgrind: ?bool = null, + pic: ?bool = null, + strip: ?bool = null, + omit_frame_pointer: ?bool = null, + stack_check: ?bool = null, + /// null means default. + /// 0 means no stack protector. + /// other number means stack protection with that buffer size. + stack_protector: ?u32 = null, + red_zone: ?bool = null, + unwind_tables: ?bool = null, + sanitize_c: ?bool = null, + sanitize_thread: ?bool = null, + }; +}; + +pub const ResolvedTarget = struct { + result: std.Target, + is_native_os: bool, + is_native_abi: bool, + llvm_cpu_features: ?[*:0]const u8 = null, +}; + +/// At least one of `parent` and `resolved_target` must be non-null. +pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { + const resolved_target = options.inherited.resolved_target orelse options.parent.?.resolved_target; + const target = resolved_target.result; + + const optimize_mode = options.inherited.optimize_mode orelse + if (options.parent) |p| p.optimize_mode else .Debug; + + const unwind_tables = options.inherited.unwind_tables orelse + if (options.parent) |p| p.unwind_tables else options.global.any_unwind_tables; + + const strip = b: { + if (options.inherited.strip) |x| break :b x; + if (options.parent) |p| break :b p.strip; + if (optimize_mode == .ReleaseSmall) break :b true; + if (!target_util.hasDebugInfo(target)) break :b true; + break :b false; + }; + + const valgrind = b: { + if (!target_util.hasValgrindSupport(target)) { + if (options.inherited.valgrind == true) + return error.ValgrindUnsupportedOnTarget; + break :b false; + } + if (options.inherited.valgrind) |x| break :b x; + if (options.parent) |p| break :b p.valgrind; + if (strip) break :b false; + break :b optimize_mode == .Debug; + }; + + const zig_backend = target_util.zigBackend(target, options.global.use_llvm); + + const single_threaded = b: { + if (target_util.alwaysSingleThreaded(target)) { + if (options.inherited.single_threaded == false) + return error.TargetRequiresSingleThreaded; + break :b true; + } + + if (options.global.have_zcu) { + if (!target_util.supportsThreads(target, zig_backend)) { + if (options.inherited.single_threaded == false) + return error.BackendRequiresSingleThreaded; + break :b true; + } + } + + if (options.inherited.single_threaded) |x| break :b x; + if (options.parent) |p| break :b p.single_threaded; + break :b target_util.defaultSingleThreaded(target); + }; + + const error_tracing = b: { + if (options.inherited.error_tracing) |x| break :b x; + if (options.parent) |p| break :b p.error_tracing; + if (strip) break :b false; + break :b switch (optimize_mode) { + .Debug => true, + .ReleaseSafe, .ReleaseFast, .ReleaseSmall => false, + }; + }; + + const pic = b: { + if (target_util.requiresPIC(target, options.global.link_libc)) { + if (options.inherited.pic == false) + return error.TargetRequiresPic; + break :b true; + } + if (options.global.pie) { + if (options.inherited.pic == false) + return error.PieRequiresPic; + break :b true; + } + if (options.global.link_mode == .Dynamic) { + if (options.inherited.pic == false) + return error.DynamicLinkingRequiresPic; + break :b true; + } + if (options.inherited.pic) |x| break :b x; + if (options.parent) |p| break :b p.pic; + break :b false; + }; + + const red_zone = b: { + if (!target_util.hasRedZone(target)) { + if (options.inherited.red_zone == true) + return error.TargetHasNoRedZone; + break :b true; + } + if (options.inherited.red_zone) |x| break :b x; + if (options.parent) |p| break :b p.red_zone; + break :b true; + }; + + const omit_frame_pointer = b: { + if (options.inherited.omit_frame_pointer) |x| break :b x; + if (options.parent) |p| break :b p.omit_frame_pointer; + if (optimize_mode == .Debug) break :b false; + break :b true; + }; + + const sanitize_thread = b: { + if (options.inherited.sanitize_thread) |x| break :b x; + if (options.parent) |p| break :b p.sanitize_thread; + break :b false; + }; + + const code_model = b: { + if (options.inherited.code_model) |x| break :b x; + if (options.parent) |p| break :b p.code_model; + break :b .default; + }; + + const is_safe_mode = switch (optimize_mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, + }; + + const sanitize_c = b: { + if (options.inherited.sanitize_c) |x| break :b x; + if (options.parent) |p| break :b p.sanitize_c; + break :b is_safe_mode; + }; + + const stack_check = b: { + if (!target_util.supportsStackProbing(target)) { + if (options.inherited.stack_check == true) + return error.StackCheckUnsupportedByTarget; + break :b false; + } + if (options.inherited.stack_check) |x| break :b x; + if (options.parent) |p| break :b p.stack_check; + break :b is_safe_mode; + }; + + const stack_protector: u32 = sp: { + if (!target_util.supportsStackProtector(target, zig_backend)) { + if (options.inherited.stack_protector) |x| { + if (x > 0) return error.StackProtectorUnsupportedByTarget; + } + break :sp 0; + } + + // This logic is checking for linking libc because otherwise our start code + // which is trying to set up TLS (i.e. the fs/gs registers) but the stack + // protection code depends on fs/gs registers being already set up. + // If we were able to annotate start code, or perhaps the entire std lib, + // as being exempt from stack protection checks, we could change this logic + // to supporting stack protection even when not linking libc. + // TODO file issue about this + if (!options.global.link_libc) { + if (options.inherited.stack_protector) |x| { + if (x > 0) return error.StackProtectorUnavailableWithoutLibC; + } + break :sp 0; + } + + if (options.inherited.stack_protector) |x| break :sp x; + if (options.parent) |p| break :sp p.stack_protector; + if (!is_safe_mode) break :sp 0; + + break :sp target_util.default_stack_protector_buffer_size; + }; + + const llvm_cpu_features: ?[*:0]const u8 = b: { + if (resolved_target.llvm_cpu_features) |x| break :b x; + if (!options.global.use_llvm) break :b null; + + var buf = std.ArrayList(u8).init(arena); + for (target.cpu.arch.allFeaturesList(), 0..) |feature, index_usize| { + const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize)); + const is_enabled = target.cpu.features.isEnabled(index); + + if (feature.llvm_name) |llvm_name| { + const plus_or_minus = "-+"[@intFromBool(is_enabled)]; + try buf.ensureUnusedCapacity(2 + llvm_name.len); + buf.appendAssumeCapacity(plus_or_minus); + buf.appendSliceAssumeCapacity(llvm_name); + buf.appendSliceAssumeCapacity(","); + } + } + if (buf.items.len == 0) break :b ""; + assert(std.mem.endsWith(u8, buf.items, ",")); + buf.items[buf.items.len - 1] = 0; + buf.shrinkAndFree(buf.items.len); + break :b buf.items[0 .. buf.items.len - 1 :0].ptr; + }; + + const builtin_mod = options.builtin_mod orelse b: { + const generated_builtin_source = try Builtin.generate(.{ + .target = target, + .zig_backend = zig_backend, + .output_mode = options.global.output_mode, + .link_mode = options.global.link_mode, + .is_test = options.global.is_test, + .test_evented_io = options.global.test_evented_io, + .single_threaded = single_threaded, + .link_libc = options.global.link_libc, + .link_libcpp = options.global.link_libcpp, + .optimize_mode = optimize_mode, + .error_tracing = error_tracing, + .valgrind = valgrind, + .sanitize_thread = sanitize_thread, + .pic = pic, + .pie = options.global.pie, + .strip = strip, + .code_model = code_model, + .omit_frame_pointer = omit_frame_pointer, + .wasi_exec_model = options.global.wasi_exec_model, + }, arena); + + const digest = Cache.HashHelper.oneShot(generated_builtin_source); + const builtin_sub_path = try arena.dupe(u8, "b" ++ std.fs.path.sep_str ++ digest); + const new = try arena.create(Module); + new.* = .{ + .root = .{ + .root_dir = options.global_cache_directory, + .sub_path = builtin_sub_path, + }, + .root_src_path = "builtin.zig", + .fully_qualified_name = if (options.parent == null) + "builtin" + else + try std.fmt.allocPrint(arena, "{s}.builtin", .{options.fully_qualified_name}), + .resolved_target = .{ + .result = target, + .is_native_os = resolved_target.is_native_os, + .is_native_abi = resolved_target.is_native_abi, + .llvm_cpu_features = llvm_cpu_features, + }, + .optimize_mode = optimize_mode, + .single_threaded = single_threaded, + .error_tracing = error_tracing, + .valgrind = valgrind, + .pic = pic, + .strip = strip, + .omit_frame_pointer = omit_frame_pointer, + .stack_check = stack_check, + .stack_protector = stack_protector, + .code_model = code_model, + .red_zone = red_zone, + .generated_builtin_source = generated_builtin_source, + .sanitize_c = sanitize_c, + .sanitize_thread = sanitize_thread, + .unwind_tables = unwind_tables, + .cc_argv = &.{}, + }; + break :b new; + }; + + const mod = try arena.create(Module); + mod.* = .{ + .root = options.paths.root, + .root_src_path = options.paths.root_src_path, + .fully_qualified_name = options.fully_qualified_name, + .resolved_target = .{ + .result = target, + .is_native_os = resolved_target.is_native_os, + .is_native_abi = resolved_target.is_native_abi, + .llvm_cpu_features = llvm_cpu_features, + }, + .optimize_mode = optimize_mode, + .single_threaded = single_threaded, + .error_tracing = error_tracing, + .valgrind = valgrind, + .pic = pic, + .strip = strip, + .omit_frame_pointer = omit_frame_pointer, + .stack_check = stack_check, + .stack_protector = stack_protector, + .code_model = code_model, + .red_zone = red_zone, + .generated_builtin_source = builtin_mod.generated_builtin_source, + .sanitize_c = sanitize_c, + .sanitize_thread = sanitize_thread, + .unwind_tables = unwind_tables, + .cc_argv = options.cc_argv, + }; + + try mod.deps.ensureUnusedCapacity(arena, 1); + mod.deps.putAssumeCapacityNoClobber("builtin", builtin_mod); + + return mod; +} + +/// All fields correspond to `CreateOptions`. +pub const LimitedOptions = struct { + root: Package.Path, + root_src_path: []const u8, + fully_qualified_name: []const u8, +}; + +/// This one can only be used if the Module will only be used for AstGen and earlier in +/// the pipeline. Illegal behavior occurs if a limited module touches Sema. +pub fn createLimited(gpa: Allocator, options: LimitedOptions) Allocator.Error!*Package.Module { + const mod = try gpa.create(Module); + mod.* = .{ + .root = options.root, + .root_src_path = options.root_src_path, + .fully_qualified_name = options.fully_qualified_name, + + .resolved_target = undefined, + .optimize_mode = undefined, + .code_model = undefined, + .single_threaded = undefined, + .error_tracing = undefined, + .valgrind = undefined, + .pic = undefined, + .strip = undefined, + .omit_frame_pointer = undefined, + .stack_check = undefined, + .stack_protector = undefined, + .red_zone = undefined, + .sanitize_c = undefined, + .sanitize_thread = undefined, + .unwind_tables = undefined, + .cc_argv = undefined, + .generated_builtin_source = undefined, + }; + return mod; +} + +pub fn getBuiltinDependency(m: *Module) *Module { + return m.deps.values()[0]; } const Module = @This(); @@ -32,3 +424,8 @@ const Package = @import("../Package.zig"); const std = @import("std"); const Allocator = std.mem.Allocator; const MultiHashHexDigest = Package.Manifest.MultiHashHexDigest; +const target_util = @import("../target.zig"); +const Cache = std.Build.Cache; +const Builtin = @import("../Builtin.zig"); +const assert = std.debug.assert; +const Compilation = @import("../Compilation.zig"); diff --git a/src/Sema.zig b/src/Sema.zig index 516fee4543da04a06fa06a40a5f6fabc5578e05b..9d1f65584ed59a942b6d530ded2aeb4df41ac678 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -36668,7 +36668,7 @@ fn getBuiltinDecl(sema: *Sema, block: *Block, name: []const u8) CompileError!Int const mod = sema.mod; const ip = &mod.intern_pool; - const std_mod = mod.main_mod.deps.get("std").?; + const std_mod = mod.std_mod; const std_file = (mod.importPkg(std_mod) catch unreachable).file; const opt_builtin_inst = (try sema.namespaceLookupRef( block, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 70eab9489cc4acab8fe534850de012fde5ffa891..cf6f38a53ea8ceed32e1e7a2e346e3ec3cee910d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -853,16 +853,9 @@ pub const Object = struct { /// want to iterate over it while adding entries to it. pub const DITypeMap = std.AutoArrayHashMapUnmanaged(InternPool.Index, AnnotatedDITypePtr); - pub fn create(gpa: Allocator, options: link.Options) !*Object { - const obj = try gpa.create(Object); - errdefer gpa.destroy(obj); - obj.* = try Object.init(gpa, options); - return obj; - } - - pub fn init(gpa: Allocator, options: link.Options) !Object { - const llvm_target_triple = try targetTriple(gpa, options.target); - defer gpa.free(llvm_target_triple); + pub fn create(arena: Allocator, options: link.File.OpenOptions) !*Object { + const gpa = options.comp.gpa; + const llvm_target_triple = try targetTriple(arena, options.target); var builder = try Builder.init(.{ .allocator = gpa, @@ -899,19 +892,14 @@ pub const Object = struct { // TODO: the only concern I have with this is WASI as either host or target, should // we leave the paths as relative then? const compile_unit_dir_z = blk: { - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; if (options.module) |mod| m: { - const d = try mod.root_mod.root.joinStringZ(builder.gpa, ""); + const d = try mod.root_mod.root.joinStringZ(arena, ""); if (d.len == 0) break :m; if (std.fs.path.isAbsolute(d)) break :blk d; - const abs = std.fs.realpath(d, &buf) catch break :blk d; - builder.gpa.free(d); - break :blk try builder.gpa.dupeZ(u8, abs); + break :blk std.fs.realpathAlloc(arena, d) catch d; } - const cwd = try std.process.getCwd(&buf); - break :blk try builder.gpa.dupeZ(u8, cwd); + break :blk try std.process.getCwdAlloc(arena); }; - defer builder.gpa.free(compile_unit_dir_z); builder.llvm.di_compile_unit = builder.llvm.di_builder.?.createCompileUnit( DW.LANG.C99, @@ -989,7 +977,8 @@ pub const Object = struct { } } - return .{ + const obj = try arena.create(Object); + obj.* = .{ .gpa = gpa, .builder = builder, .module = options.module.?, @@ -1009,9 +998,11 @@ pub const Object = struct { .null_opt_usize = .no_init, .struct_field_map = .{}, }; + return obj; } - pub fn deinit(self: *Object, gpa: Allocator) void { + pub fn deinit(self: *Object) void { + const gpa = self.gpa; self.di_map.deinit(gpa); self.di_type_map.deinit(gpa); if (self.builder.useLibLlvm()) { @@ -1028,11 +1019,6 @@ pub const Object = struct { self.* = undefined; } - pub fn destroy(self: *Object, gpa: Allocator) void { - self.deinit(gpa); - gpa.destroy(self); - } - fn locPath( arena: Allocator, opt_loc: ?Compilation.EmitLoc, @@ -2899,7 +2885,7 @@ pub const Object = struct { fn getStackTraceType(o: *Object) Allocator.Error!Type { const mod = o.module; - const std_mod = mod.main_mod.deps.get("std").?; + const std_mod = mod.std_mod; const std_file = (mod.importPkg(std_mod) catch unreachable).file; const builtin_str = try mod.intern_pool.getOrPutString(mod.gpa, "builtin"); diff --git a/src/link.zig b/src/link.zig index 1ad66eaf7affa20feadc8cac58ced9e1516e216e..46dab1b6edbf78a2767ad817309650822d8a9a5a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -66,237 +66,18 @@ pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void { pub const producer_string = if (builtin.is_test) "zig test" else "zig " ++ build_options.version; -pub const Emit = struct { - /// Where the output will go. - directory: Compilation.Directory, - /// Path to the output file, relative to `directory`. - sub_path: []const u8, - - /// Returns the full path to `basename` if it were in the same directory as the - /// `Emit` sub_path. - pub fn basenamePath(emit: Emit, arena: Allocator, basename: [:0]const u8) ![:0]const u8 { - const full_path = if (emit.directory.path) |p| - try fs.path.join(arena, &[_][]const u8{ p, emit.sub_path }) - else - emit.sub_path; - - if (fs.path.dirname(full_path)) |dirname| { - return try fs.path.joinZ(arena, &.{ dirname, basename }); - } else { - return basename; - } - } -}; - -pub const Options = struct { - /// This is `null` when `-fno-emit-bin` is used. - emit: ?Emit, - /// This is `null` when not building a Windows DLL, or when `-fno-emit-implib` is used. - implib_emit: ?Emit, - /// This is non-null when `-femit-docs` is provided. - docs_emit: ?Emit, - target: std.Target, - output_mode: std.builtin.OutputMode, - link_mode: std.builtin.LinkMode, - optimize_mode: std.builtin.OptimizeMode, - machine_code_model: std.builtin.CodeModel, - root_name: [:0]const u8, - /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. - module: ?*Module, - /// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin) - sysroot: ?[]const u8, - /// Used for calculating how much space to reserve for symbols in case the binary file - /// does not already have a symbol table. - symbol_count_hint: u64 = 32, - /// Used for calculating how much space to reserve for executable program code in case - /// the binary file does not already have such a section. - program_code_size_hint: u64 = 256 * 1024, - entry_addr: ?u64 = null, - entry: ?[]const u8, - stack_size_override: ?u64, - image_base_override: ?u64, - /// 0 means no stack protector - /// other value means stack protector with that buffer size. - stack_protector: u32, - cache_mode: CacheMode, - include_compiler_rt: bool, - /// Set to `true` to omit debug info. - strip: bool, - /// If this is true then this link 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`) this link code directly outputs and updates the final binary. - use_lld: bool, - /// If this is true then this link 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`) this link code directly outputs and updates the final binary. - use_llvm: bool, - use_lib_llvm: bool, - link_libc: bool, - link_libcpp: bool, - link_libunwind: bool, - darwin_sdk_layout: ?DarwinSdkLayout, - function_sections: bool, - data_sections: bool, - no_builtin: bool, - eh_frame_hdr: bool, - emit_relocs: bool, - rdynamic: bool, - z_nodelete: bool, - z_notext: bool, - z_defs: bool, - z_origin: bool, - z_nocopyreloc: bool, - z_now: bool, - z_relro: bool, - z_common_page_size: ?u64, - z_max_page_size: ?u64, - tsaware: bool, - nxcompat: bool, - dynamicbase: bool, - linker_optimization: u8, - compress_debug_sections: CompressDebugSections, - bind_global_refs_locally: bool, - import_memory: bool, - export_memory: bool, - import_symbols: bool, - import_table: bool, - export_table: bool, - initial_memory: ?u64, - max_memory: ?u64, - shared_memory: bool, - export_symbol_names: []const []const u8, - global_base: ?u64, - is_native_os: bool, - is_native_abi: bool, - pic: bool, - pie: bool, - lto: bool, - valgrind: bool, - tsan: bool, - stack_check: bool, - red_zone: bool, - omit_frame_pointer: bool, - single_threaded: bool, - verbose_link: bool, - dll_export_fns: bool, - error_return_tracing: bool, - skip_linker_dependencies: bool, - each_lib_rpath: bool, - build_id: std.zig.BuildId, - disable_lld_caching: bool, - is_test: bool, - hash_style: HashStyle, - sort_section: ?SortSection, - major_subsystem_version: ?u32, - minor_subsystem_version: ?u32, - gc_sections: ?bool = null, - allow_shlib_undefined: ?bool, - subsystem: ?std.Target.SubSystem, - linker_script: ?[]const u8, - version_script: ?[]const u8, - soname: ?[]const u8, - llvm_cpu_features: ?[*:0]const u8, - print_gc_sections: bool, - print_icf_sections: bool, - print_map: bool, - opt_bisect_limit: i32, - - objects: []Compilation.LinkObject, - framework_dirs: []const []const u8, - frameworks: []const Framework, - /// These are *always* dynamically linked. Static libraries will be - /// provided as positional arguments. - system_libs: std.StringArrayHashMapUnmanaged(SystemLib), - wasi_emulated_libs: []const wasi_libc.CRTFile, - // TODO: remove this. libraries are resolved by the frontend. - lib_dirs: []const []const u8, - rpath_list: []const []const u8, - - /// List of symbols forced as undefined in the symbol table - /// thus forcing their resolution by the linker. - /// Corresponds to `-u ` for ELF/MachO and `/include:` for COFF/PE. - force_undefined_symbols: std.StringArrayHashMapUnmanaged(void), - /// Use a wrapper function for symbol. Any undefined reference to symbol - /// will be resolved to __wrap_symbol. Any undefined reference to - /// __real_symbol will be resolved to symbol. This can be used to provide a - /// wrapper for a system function. The wrapper function should be called - /// __wrap_symbol. If it wishes to call the system function, it should call - /// __real_symbol. - symbol_wrap_set: std.StringArrayHashMapUnmanaged(void), - - version: ?std.SemanticVersion, - compatibility_version: ?std.SemanticVersion, - libc_installation: ?*const LibCInstallation, - - dwarf_format: ?std.dwarf.Format, - - /// WASI-only. Type of WASI execution model ("command" or "reactor"). - wasi_exec_model: std.builtin.WasiExecModel = undefined, - - /// (Zig compiler development) Enable dumping of linker's state as JSON. - enable_link_snapshots: bool = false, - - /// (Darwin) Install name for the dylib - install_name: ?[]const u8 = null, - - /// (Darwin) Path to entitlements file - entitlements: ?[]const u8 = null, - - /// (Darwin) size of the __PAGEZERO segment - pagezero_size: ?u64 = null, - - /// (Darwin) set minimum space for future expansion of the load commands - headerpad_size: ?u32 = null, - - /// (Darwin) set enough space as if all paths were MATPATHLEN - headerpad_max_install_names: bool = false, - - /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols - dead_strip_dylibs: bool = false, - - /// (Windows) PDB source path prefix to instruct the linker how to resolve relative - /// paths when consolidating CodeView streams into a single PDB file. - pdb_source_path: ?[]const u8 = null, - - /// (Windows) PDB output path - pdb_out_path: ?[]const u8 = null, - - /// (Windows) .def file to specify when linking - module_definition_file: ?[]const u8 = null, - - /// (SPIR-V) whether to generate a structured control flow graph or not - want_structured_cfg: ?bool = null, - - pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode { - return if (options.use_lld) .Obj else options.output_mode; - } - - pub fn move(self: *Options) Options { - const copied_state = self.*; - self.system_libs = .{}; - self.force_undefined_symbols = .{}; - return copied_state; - } -}; - pub const HashStyle = enum { sysv, gnu, both }; pub const CompressDebugSections = enum { none, zlib, zstd }; -/// The filesystem layout of darwin SDK elements. -pub const DarwinSdkLayout = enum { - /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }. - sdk, - /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, }. - vendored, -}; - pub const File = struct { tag: Tag, - options: Options, + + /// The owner of this output File. + comp: *Compilation, + emit: Compilation.Emit, + file: ?fs.File, - allocator: Allocator, /// When linking with LLD, this linker code will output an object file only at /// this location, and then this path can be placed on the LLD linker line. intermediary_basename: ?[]const u8 = null, @@ -307,103 +88,132 @@ pub const File = struct { child_pid: ?std.ChildProcess.Id = null, + pub const OpenOptions = struct { + comp: *Compilation, + emit: Compilation.Emit, + + symbol_count_hint: u64 = 32, + program_code_size_hint: u64 = 256 * 1024, + + /// Virtual address of the entry point procedure relative to image base. + entry_addr: ?u64, + stack_size_override: ?u64, + image_base_override: ?u64, + function_sections: bool, + data_sections: bool, + no_builtin: bool, + eh_frame_hdr: bool, + emit_relocs: bool, + rdynamic: bool, + optimization: u8, + linker_script: ?[]const u8, + z_nodelete: bool, + z_notext: bool, + z_defs: bool, + z_origin: bool, + z_nocopyreloc: bool, + z_now: bool, + z_relro: bool, + z_common_page_size: ?u64, + z_max_page_size: ?u64, + tsaware: bool, + nxcompat: bool, + dynamicbase: bool, + compress_debug_sections: CompressDebugSections, + bind_global_refs_locally: bool, + import_symbols: bool, + import_table: bool, + export_table: bool, + initial_memory: ?u64, + max_memory: ?u64, + export_symbol_names: []const []const u8, + global_base: ?u64, + verbose_link: bool, + dll_export_fns: bool, + skip_linker_dependencies: bool, + parent_compilation_link_libc: bool, + each_lib_rpath: bool, + build_id: std.zig.BuildId, + disable_lld_caching: bool, + hash_style: HashStyle, + sort_section: ?SortSection, + major_subsystem_version: ?u32, + minor_subsystem_version: ?u32, + gc_sections: ?bool = null, + allow_shlib_undefined: ?bool, + subsystem: ?std.Target.SubSystem, + version_script: ?[]const u8, + soname: ?[]const u8, + print_gc_sections: bool, + print_icf_sections: bool, + print_map: bool, + opt_bisect_limit: i32, + + /// List of symbols forced as undefined in the symbol table + /// thus forcing their resolution by the linker. + /// Corresponds to `-u ` for ELF/MachO and `/include:` for COFF/PE. + force_undefined_symbols: std.StringArrayHashMapUnmanaged(void), + /// Use a wrapper function for symbol. Any undefined reference to symbol + /// will be resolved to __wrap_symbol. Any undefined reference to + /// __real_symbol will be resolved to symbol. This can be used to provide a + /// wrapper for a system function. The wrapper function should be called + /// __wrap_symbol. If it wishes to call the system function, it should call + /// __real_symbol. + symbol_wrap_set: std.StringArrayHashMapUnmanaged(void), + + compatibility_version: ?std.SemanticVersion, + + dwarf_format: ?std.dwarf.Format, + + // TODO: remove this. libraries are resolved by the frontend. + lib_dirs: []const []const u8, + rpath_list: []const []const u8, + + /// (Zig compiler development) Enable dumping of linker's state as JSON. + enable_link_snapshots: bool, + + /// (Darwin) Install name for the dylib + install_name: ?[]const u8, + /// (Darwin) Path to entitlements file + entitlements: ?[]const u8, + /// (Darwin) size of the __PAGEZERO segment + pagezero_size: ?u64, + /// (Darwin) set minimum space for future expansion of the load commands + headerpad_size: ?u32, + /// (Darwin) set enough space as if all paths were MATPATHLEN + headerpad_max_install_names: bool, + /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols + dead_strip_dylibs: bool, + framework_dirs: []const []const u8, + frameworks: []const Framework, + darwin_sdk_layout: ?MachO.SdkLayout, + + /// (Windows) PDB source path prefix to instruct the linker how to resolve relative + /// paths when consolidating CodeView streams into a single PDB file. + pdb_source_path: ?[]const u8, + /// (Windows) PDB output path + pdb_out_path: ?[]const u8, + /// (Windows) .def file to specify when linking + module_definition_file: ?[]const u8, + + /// (SPIR-V) whether to generate a structured control flow graph or not + want_structured_cfg: ?bool, + + wasi_emulated_libs: []const wasi_libc.CRTFile, + }; + /// Attempts incremental linking, if the file already exists. If /// incremental linking fails, falls back to truncating the file and /// rewriting it. A malicious file is detected as incremental link failure /// and does not cause Illegal Behavior. This operation is not atomic. - pub fn openPath(allocator: Allocator, options: Options) !*File { - const have_macho = !build_options.only_c; - if (have_macho and options.target.ofmt == .macho) { - return &(try MachO.openPath(allocator, options)).base; + /// `arena` is used for allocations with the same lifetime as the created File. + pub fn open(arena: Allocator, options: OpenOptions) !*File { + switch (Tag.fromObjectFormat(options.comp.root_mod.resolved_target.result.ofmt)) { + inline else => |tag| { + const ptr = try tag.Type().open(arena, options); + return &ptr.base; + }, } - - if (options.emit == null) { - return switch (options.target.ofmt) { - .coff => &(try Coff.createEmpty(allocator, options)).base, - .elf => &(try Elf.createEmpty(allocator, options)).base, - .macho => unreachable, - .wasm => &(try Wasm.createEmpty(allocator, options)).base, - .plan9 => return &(try Plan9.createEmpty(allocator, options)).base, - .c => unreachable, // Reported error earlier. - .spirv => &(try SpirV.createEmpty(allocator, options)).base, - .nvptx => &(try NvPtx.createEmpty(allocator, options)).base, - .hex => return error.HexObjectFormatUnimplemented, - .raw => return error.RawObjectFormatUnimplemented, - .dxcontainer => return error.DirectXContainerObjectFormatUnimplemented, - }; - } - const emit = options.emit.?; - const use_lld = build_options.have_llvm and options.use_lld; // comptime-known false when !have_llvm - const sub_path = if (use_lld) blk: { - if (options.module == null) { - // No point in opening a file, we would not write anything to it. - // Initialize with empty. - return switch (options.target.ofmt) { - .coff => &(try Coff.createEmpty(allocator, options)).base, - .elf => &(try Elf.createEmpty(allocator, options)).base, - .macho => unreachable, - .plan9 => &(try Plan9.createEmpty(allocator, options)).base, - .wasm => &(try Wasm.createEmpty(allocator, options)).base, - .c => unreachable, // Reported error earlier. - .spirv => &(try SpirV.createEmpty(allocator, options)).base, - .nvptx => &(try NvPtx.createEmpty(allocator, options)).base, - .hex => return error.HexObjectFormatUnimplemented, - .raw => return error.RawObjectFormatUnimplemented, - .dxcontainer => return error.DirectXContainerObjectFormatUnimplemented, - }; - } - // Open a temporary object file, not the final output file because we - // want to link with LLD. - break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ - emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), - }); - } else emit.sub_path; - errdefer if (use_lld) allocator.free(sub_path); - - const file: *File = f: { - switch (options.target.ofmt) { - .coff => { - if (build_options.only_c) unreachable; - break :f &(try Coff.openPath(allocator, sub_path, options)).base; - }, - .elf => { - if (build_options.only_c) unreachable; - break :f &(try Elf.openPath(allocator, sub_path, options)).base; - }, - .macho => unreachable, - .plan9 => { - if (build_options.only_c) unreachable; - break :f &(try Plan9.openPath(allocator, sub_path, options)).base; - }, - .wasm => { - if (build_options.only_c) unreachable; - break :f &(try Wasm.openPath(allocator, sub_path, options)).base; - }, - .c => { - break :f &(try C.openPath(allocator, sub_path, options)).base; - }, - .spirv => { - if (build_options.only_c) unreachable; - break :f &(try SpirV.openPath(allocator, sub_path, options)).base; - }, - .nvptx => { - if (build_options.only_c) unreachable; - break :f &(try NvPtx.openPath(allocator, sub_path, options)).base; - }, - .hex => return error.HexObjectFormatUnimplemented, - .raw => return error.RawObjectFormatUnimplemented, - .dxcontainer => return error.DirectXContainerObjectFormatUnimplemented, - } - }; - - if (use_lld) { - // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`, - // we also want to put the intermediary object file in the cache while the - // main emit directory is the cwd. - file.intermediary_basename = sub_path; - } - - return file; } pub fn cast(base: *File, comptime T: type) ?*T { @@ -664,56 +474,45 @@ pub const File = struct { pub fn destroy(base: *File) void { base.releaseLock(); if (base.file) |f| f.close(); - if (base.intermediary_basename) |sub_path| base.allocator.free(sub_path); - base.options.system_libs.deinit(base.allocator); - base.options.force_undefined_symbols.deinit(base.allocator); switch (base.tag) { .coff => { if (build_options.only_c) unreachable; const parent = @fieldParentPtr(Coff, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, .elf => { if (build_options.only_c) unreachable; const parent = @fieldParentPtr(Elf, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, .macho => { if (build_options.only_c) unreachable; const parent = @fieldParentPtr(MachO, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, .c => { const parent = @fieldParentPtr(C, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, .wasm => { if (build_options.only_c) unreachable; const parent = @fieldParentPtr(Wasm, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, .spirv => { if (build_options.only_c) unreachable; const parent = @fieldParentPtr(SpirV, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, .plan9 => { if (build_options.only_c) unreachable; const parent = @fieldParentPtr(Plan9, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, .nvptx => { if (build_options.only_c) unreachable; const parent = @fieldParentPtr(NvPtx, "base", base); parent.deinit(); - base.allocator.destroy(parent); }, } } @@ -1197,6 +996,35 @@ pub const File = struct { spirv, plan9, nvptx, + + pub fn Type(comptime tag: Tag) type { + return switch (tag) { + .coff => Coff, + .elf => Elf, + .macho => MachO, + .c => C, + .wasm => Wasm, + .spirv => SpirV, + .plan9 => Plan9, + .nvptx => NvPtx, + }; + } + + pub fn fromObjectFormat(ofmt: std.Target.ObjectFormat) Tag { + return switch (ofmt) { + .coff => .coff, + .elf => .elf, + .macho => .macho, + .wasm => .wasm, + .plan9 => .plan9, + .c => .c, + .spirv => .spirv, + .nvptx => .nvptx, + .hex => @panic("TODO implement hex object format"), + .raw => @panic("TODO implement raw object format"), + .dxcontainer => @panic("TODO implement dxcontainer object format"), + }; + } }; pub const ErrorFlags = struct { @@ -1235,6 +1063,33 @@ pub const File = struct { } }; + pub fn effectiveOutputMode( + use_lld: bool, + output_mode: std.builtin.OutputMode, + ) std.builtin.OutputMode { + return if (use_lld) .Obj else output_mode; + } + + pub fn determineMode( + use_lld: bool, + output_mode: std.builtin.OutputMode, + link_mode: std.builtin.LinkMode, + ) fs.File.Mode { + // On common systems with a 0o022 umask, 0o777 will still result in a file created + // with 0o755 permissions, but it works appropriately if the system is configured + // more leniently. As another data point, C's fopen seems to open files with the + // 666 mode. + const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777; + switch (effectiveOutputMode(use_lld, output_mode)) { + .Lib => return switch (link_mode) { + .Dynamic => executable_mode, + .Static => fs.File.default_mode, + }, + .Exe => return executable_mode, + .Obj => return fs.File.default_mode, + } + } + pub const C = @import("link/C.zig"); pub const Coff = @import("link/Coff.zig"); pub const Plan9 = @import("link/Plan9.zig"); @@ -1245,19 +1100,3 @@ pub const File = struct { pub const NvPtx = @import("link/NvPtx.zig"); pub const Dwarf = @import("link/Dwarf.zig"); }; - -pub fn determineMode(options: Options) fs.File.Mode { - // On common systems with a 0o022 umask, 0o777 will still result in a file created - // with 0o755 permissions, but it works appropriately if the system is configured - // more leniently. As another data point, C's fopen seems to open files with the - // 666 mode. - const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777; - switch (options.effectiveOutputMode()) { - .Lib => return switch (options.link_mode) { - .Dynamic => executable_mode, - .Static => fs.File.default_mode, - }, - .Exe => return executable_mode, - .Obj => return fs.File.default_mode, - } -} diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 5fbf02871adfbc511855fdd266e4868532c3c601..e477aeffcf480a152ee0af3c0d19a72eb599b7b0 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -48,9 +48,6 @@ got_table_count_dirty: bool = true, got_table_contents_dirty: bool = true, imports_count_dirty: bool = true, -/// Virtual address of the entry point procedure relative to image base. -entry_addr: ?u32 = null, - /// Table of tracked LazySymbols. lazy_syms: LazySymbolTable = .{}, @@ -226,44 +223,150 @@ const ideal_factor = 3; const minimum_text_block_size = 64; pub const min_text_capacity = padToIdeal(minimum_text_block_size); -pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { - assert(options.target.ofmt == .coff); +pub fn open(arena: Allocator, options: link.File.OpenOptions) !*Coff { + if (build_options.only_c) unreachable; + const target = options.comp.root_mod.resolved_target.result; + assert(target.ofmt == .coff); - if (options.use_llvm) { - return createEmpty(allocator, options); - } - - const self = try createEmpty(allocator, options); + const self = try createEmpty(arena, options); errdefer self.base.destroy(); - const file = try options.emit.?.directory.handle.createFile(sub_path, .{ + const use_lld = build_options.have_llvm and options.comp.config.use_lld; + const use_llvm = build_options.have_llvm and options.comp.config.use_llvm; + + if (use_lld and use_llvm) { + // LLVM emits the object file; LLD links it into the final product. + return self; + } + + const sub_path = if (!use_lld) options.emit.sub_path else p: { + // Open a temporary object file, not the final output file because we + // want to link with LLD. + const o_file_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ + options.emit.sub_path, target.ofmt.fileExt(target.cpu.arch), + }); + self.base.intermediary_basename = o_file_path; + break :p o_file_path; + }; + + self.base.file = try options.emit.directory.handle.createFile(sub_path, .{ .truncate = false, .read = true, - .mode = link.determineMode(options), + .mode = link.File.determineMode( + use_lld, + options.comp.config.output_mode, + options.comp.config.link_mode, + ), }); - self.base.file = file; - try self.populateMissingMetadata(); + assert(self.llvm_object == null); + const gpa = self.base.comp.gpa; + + try self.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32)); + self.strtab.buffer.appendNTimesAssumeCapacity(0, @sizeOf(u32)); + + try self.temp_strtab.buffer.append(gpa, 0); + + // Index 0 is always a null symbol. + try self.locals.append(gpa, .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = .UNDEFINED, + .type = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }); + + if (self.text_section_index == null) { + const file_size: u32 = @intCast(options.program_code_size_hint); + self.text_section_index = try self.allocateSection(".text", file_size, .{ + .CNT_CODE = 1, + .MEM_EXECUTE = 1, + .MEM_READ = 1, + }); + } + + if (self.got_section_index == null) { + const file_size = @as(u32, @intCast(options.symbol_count_hint)) * self.ptr_width.size(); + self.got_section_index = try self.allocateSection(".got", file_size, .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }); + } + + if (self.rdata_section_index == null) { + const file_size: u32 = self.page_size; + self.rdata_section_index = try self.allocateSection(".rdata", file_size, .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }); + } + + if (self.data_section_index == null) { + const file_size: u32 = self.page_size; + self.data_section_index = try self.allocateSection(".data", file_size, .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + .MEM_WRITE = 1, + }); + } + + if (self.idata_section_index == null) { + const file_size = @as(u32, @intCast(options.symbol_count_hint)) * self.ptr_width.size(); + self.idata_section_index = try self.allocateSection(".idata", file_size, .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }); + } + + if (self.reloc_section_index == null) { + const file_size = @as(u32, @intCast(options.symbol_count_hint)) * @sizeOf(coff.BaseRelocation); + self.reloc_section_index = try self.allocateSection(".reloc", file_size, .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_DISCARDABLE = 1, + .MEM_READ = 1, + }); + } + + if (self.strtab_offset == null) { + const file_size = @as(u32, @intCast(self.strtab.buffer.items.len)); + self.strtab_offset = self.findFreeSpace(file_size, @alignOf(u32)); // 4bytes aligned seems like a good idea here + log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + file_size }); + } + + { + // We need to find out what the max file offset is according to section headers. + // Otherwise, we may end up with an COFF binary with file size not matching the final section's + // offset + it's filesize. + // TODO I don't like this here one bit + var max_file_offset: u64 = 0; + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) { + max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; + } + } + try self.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset); + } return self; } -pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { - const ptr_width: PtrWidth = switch (options.target.ptrBitWidth()) { +pub fn createEmpty(arena: Allocator, options: link.File.OpenOptions) !*Coff { + const target = options.comp.root_mod.resolved_target.result; + const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { 0...32 => .p32, 33...64 => .p64, else => return error.UnsupportedCOFFArchitecture, }; - const page_size: u32 = switch (options.target.cpu.arch) { + const page_size: u32 = switch (target.cpu.arch) { else => 0x1000, }; - const self = try gpa.create(Coff); - errdefer gpa.destroy(self); + const self = try arena.create(Coff); self.* = .{ .base = .{ .tag = .coff, - .options = options, - .allocator = gpa, + .comp = options.comp, + .emit = options.emit, .file = null, }, .ptr_width = ptr_width, @@ -271,16 +374,17 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { .data_directories = comptime mem.zeroes([coff.IMAGE_NUMBEROF_DIRECTORY_ENTRIES]coff.ImageDataDirectory), }; - if (options.use_llvm) { - self.llvm_object = try LlvmObject.create(gpa, options); + const use_llvm = build_options.have_llvm and options.comp.config.use_llvm; + if (use_llvm and options.comp.config.have_zcu) { + self.llvm_object = try LlvmObject.create(arena, options); } return self; } pub fn deinit(self: *Coff) void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; - if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); + if (self.llvm_object) |llvm_object| llvm_object.deinit(); for (self.objects.items) |*object| { object.deinit(gpa); @@ -349,97 +453,6 @@ pub fn deinit(self: *Coff) void { self.base_relocs.deinit(gpa); } -fn populateMissingMetadata(self: *Coff) !void { - assert(self.llvm_object == null); - const gpa = self.base.allocator; - - try self.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32)); - self.strtab.buffer.appendNTimesAssumeCapacity(0, @sizeOf(u32)); - - try self.temp_strtab.buffer.append(gpa, 0); - - // Index 0 is always a null symbol. - try self.locals.append(gpa, .{ - .name = [_]u8{0} ** 8, - .value = 0, - .section_number = .UNDEFINED, - .type = .{ .base_type = .NULL, .complex_type = .NULL }, - .storage_class = .NULL, - .number_of_aux_symbols = 0, - }); - - if (self.text_section_index == null) { - const file_size = @as(u32, @intCast(self.base.options.program_code_size_hint)); - self.text_section_index = try self.allocateSection(".text", file_size, .{ - .CNT_CODE = 1, - .MEM_EXECUTE = 1, - .MEM_READ = 1, - }); - } - - if (self.got_section_index == null) { - const file_size = @as(u32, @intCast(self.base.options.symbol_count_hint)) * self.ptr_width.size(); - self.got_section_index = try self.allocateSection(".got", file_size, .{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - }); - } - - if (self.rdata_section_index == null) { - const file_size: u32 = self.page_size; - self.rdata_section_index = try self.allocateSection(".rdata", file_size, .{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - }); - } - - if (self.data_section_index == null) { - const file_size: u32 = self.page_size; - self.data_section_index = try self.allocateSection(".data", file_size, .{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - .MEM_WRITE = 1, - }); - } - - if (self.idata_section_index == null) { - const file_size = @as(u32, @intCast(self.base.options.symbol_count_hint)) * self.ptr_width.size(); - self.idata_section_index = try self.allocateSection(".idata", file_size, .{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - }); - } - - if (self.reloc_section_index == null) { - const file_size = @as(u32, @intCast(self.base.options.symbol_count_hint)) * @sizeOf(coff.BaseRelocation); - self.reloc_section_index = try self.allocateSection(".reloc", file_size, .{ - .CNT_INITIALIZED_DATA = 1, - .MEM_DISCARDABLE = 1, - .MEM_READ = 1, - }); - } - - if (self.strtab_offset == null) { - const file_size = @as(u32, @intCast(self.strtab.buffer.items.len)); - self.strtab_offset = self.findFreeSpace(file_size, @alignOf(u32)); // 4bytes aligned seems like a good idea here - log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + file_size }); - } - - { - // We need to find out what the max file offset is according to section headers. - // Otherwise, we may end up with an COFF binary with file size not matching the final section's - // offset + it's filesize. - // TODO I don't like this here one bit - var max_file_offset: u64 = 0; - for (self.sections.items(.header)) |header| { - if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) { - max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; - } - } - try self.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset); - } -} - fn allocateSection(self: *Coff, name: []const u8, size: u32, flags: coff.SectionHeaderFlags) !u16 { const index = @as(u16, @intCast(self.sections.slice().len)); const off = self.findFreeSpace(size, default_file_alignment); @@ -471,8 +484,9 @@ fn allocateSection(self: *Coff, name: []const u8, size: u32, flags: coff.Section .number_of_linenumbers = 0, .flags = flags, }; + const gpa = self.base.comp.gpa; try self.setSectionName(&header, name); - try self.sections.append(self.base.allocator, .{ .header = header }); + try self.sections.append(gpa, .{ .header = header }); return index; } @@ -654,7 +668,7 @@ fn allocateAtom(self: *Coff, atom_index: Atom.Index, new_atom_size: u32, alignme } pub fn allocateSymbol(self: *Coff) !u32 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.locals.ensureUnusedCapacity(gpa, 1); const index = blk: { @@ -682,7 +696,7 @@ pub fn allocateSymbol(self: *Coff) !u32 { } fn allocateGlobal(self: *Coff) !u32 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.globals.ensureUnusedCapacity(gpa, 1); const index = blk: { @@ -706,15 +720,16 @@ fn allocateGlobal(self: *Coff) !u32 { } fn addGotEntry(self: *Coff, target: SymbolWithLoc) !void { + const gpa = self.base.comp.gpa; if (self.got_table.lookup.contains(target)) return; - const got_index = try self.got_table.allocateEntry(self.base.allocator, target); + const got_index = try self.got_table.allocateEntry(gpa, target); try self.writeOffsetTableEntry(got_index); self.got_table_count_dirty = true; self.markRelocsDirtyByTarget(target); } pub fn createAtom(self: *Coff) !Atom.Index { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const atom_index = @as(Atom.Index, @intCast(self.atoms.items.len)); const atom = try self.atoms.addOne(gpa); const sym_index = try self.allocateSymbol(); @@ -759,7 +774,7 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void { file_offset + code.len, }); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; // Gather relocs which can be resolved. // We need to do this as we will be applying different slide values depending @@ -870,7 +885,7 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { if (is_hot_update_compatible) { if (self.base.child_pid) |handle| { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const slide = @intFromPtr(self.hot_state.loaded_base_address.?); const actual_vmaddr = vmaddr + slide; const pvaddr = @as(*anyopaque, @ptrFromInt(actual_vmaddr)); @@ -974,7 +989,7 @@ pub fn ptraceDetach(self: *Coff, handle: std.ChildProcess.Id) void { fn freeAtom(self: *Coff, atom_index: Atom.Index) void { log.debug("freeAtom {d}", .{atom_index}); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; // Remove any relocs and base relocs associated with this Atom Atom.freeRelocations(self, atom_index); @@ -1061,7 +1076,8 @@ pub fn updateFunc(self: *Coff, mod: *Module, func_index: InternPool.Index, air: self.freeUnnamedConsts(decl_index); Atom.freeRelocations(self, atom_index); - var code_buffer = std.ArrayList(u8).init(self.base.allocator); + const gpa = self.base.comp.gpa; + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); const res = try codegen.generateFunction( @@ -1090,7 +1106,7 @@ pub fn updateFunc(self: *Coff, mod: *Module, func_index: InternPool.Index, air: } pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: InternPool.DeclIndex) !u32 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); const gop = try self.unnamed_const_atoms.getOrPut(gpa, decl_index); @@ -1121,7 +1137,7 @@ const LowerConstResult = union(enum) { }; fn lowerConst(self: *Coff, name: []const u8, tv: TypedValue, required_alignment: InternPool.Alignment, sect_id: u16, src_loc: Module.SrcLoc) !LowerConstResult { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); @@ -1174,13 +1190,14 @@ pub fn updateDecl( return; } + const gpa = self.base.comp.gpa; if (decl.isExtern(mod)) { // TODO make this part of getGlobalSymbol const variable = decl.getOwnedVariable(mod).?; const name = mod.intern_pool.stringToSlice(decl.name); const lib_name = mod.intern_pool.stringToSliceUnwrap(variable.lib_name); const global_index = try self.getGlobalSymbol(name, lib_name); - try self.need_got_table.put(self.base.allocator, global_index, {}); + try self.need_got_table.put(gpa, global_index, {}); return; } @@ -1188,7 +1205,7 @@ pub fn updateDecl( Atom.freeRelocations(self, atom_index); const atom = self.getAtom(atom_index); - var code_buffer = std.ArrayList(u8).init(self.base.allocator); + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); const decl_val = if (decl.val.getVariable(mod)) |variable| Value.fromInterned(variable.init) else decl.val; @@ -1220,7 +1237,7 @@ fn updateLazySymbolAtom( atom_index: Atom.Index, section_index: u16, ) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; var required_alignment: InternPool.Alignment = .none; @@ -1281,8 +1298,9 @@ fn updateLazySymbolAtom( } pub fn getOrCreateAtomForLazySymbol(self: *Coff, sym: link.File.LazySymbol) !Atom.Index { + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; - const gop = try self.lazy_syms.getOrPut(self.base.allocator, sym.getDecl(mod)); + const gop = try self.lazy_syms.getOrPut(gpa, sym.getDecl(mod)); errdefer _ = if (!gop.found_existing) self.lazy_syms.pop(); if (!gop.found_existing) gop.value_ptr.* = .{}; const metadata: struct { atom: *Atom.Index, state: *LazySymbolMetadata.State } = switch (sym.kind) { @@ -1305,7 +1323,8 @@ pub fn getOrCreateAtomForLazySymbol(self: *Coff, sym: link.File.LazySymbol) !Ato } pub fn getOrCreateAtomForDecl(self: *Coff, decl_index: InternPool.DeclIndex) !Atom.Index { - const gop = try self.decls.getOrPut(self.base.allocator, decl_index); + const gpa = self.base.comp.gpa; + const gop = try self.decls.getOrPut(gpa, decl_index); if (!gop.found_existing) { gop.value_ptr.* = .{ .atom = try self.createAtom(), @@ -1401,7 +1420,7 @@ fn updateDeclCode(self: *Coff, decl_index: InternPool.DeclIndex, code: []u8, com } fn freeUnnamedConsts(self: *Coff, decl_index: InternPool.DeclIndex) void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const unnamed_consts = self.unnamed_const_atoms.getPtr(decl_index) orelse return; for (unnamed_consts.items) |atom_index| { self.freeAtom(atom_index); @@ -1412,6 +1431,7 @@ fn freeUnnamedConsts(self: *Coff, decl_index: InternPool.DeclIndex) void { pub fn freeDecl(self: *Coff, decl_index: InternPool.DeclIndex) void { if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index); + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); @@ -1421,7 +1441,7 @@ pub fn freeDecl(self: *Coff, decl_index: InternPool.DeclIndex) void { var kv = const_kv; self.freeAtom(kv.value.atom); self.freeUnnamedConsts(decl_index); - kv.value.exports.deinit(self.base.allocator); + kv.value.exports.deinit(gpa); } } @@ -1476,7 +1496,7 @@ pub fn updateExports( if (self.base.options.emit == null) return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const metadata = switch (exported) { .decl_index => |decl_index| blk: { @@ -1574,7 +1594,7 @@ pub fn deleteDeclExport( const name = mod.intern_pool.stringToSlice(name_ip); const sym_index = metadata.getExportPtr(self, name) orelse return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const sym_loc = SymbolWithLoc{ .sym_index = sym_index.*, .file = null }; const sym = self.getSymbolPtr(sym_loc); log.debug("deleting export '{s}'", .{name}); @@ -1602,7 +1622,7 @@ pub fn deleteDeclExport( } fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const sym = self.getSymbol(current); const sym_name = self.getSymbolName(current); @@ -1653,7 +1673,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; @@ -1794,7 +1814,7 @@ pub fn lowerAnonDecl( explicit_alignment: InternPool.Alignment, src_loc: Module.SrcLoc, ) !codegen.Result { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val)); const decl_alignment = switch (explicit_alignment) { @@ -1868,7 +1888,7 @@ pub fn getGlobalSymbol(self: *Coff, name: []const u8, lib_name_name: ?[]const u8 const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; gop.value_ptr.* = sym_loc; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const sym = self.getSymbolPtr(sym_loc); try self.setSymbolName(sym, name); sym.storage_class = .EXTERNAL; @@ -1895,7 +1915,7 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl_index: InternPool /// TODO: note that .ABSOLUTE is used as padding within each block; we could use this fact to do /// incremental updates and writes into the table instead of doing it all at once fn writeBaseRelocations(self: *Coff) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var page_table = std.AutoHashMap(u32, std.ArrayList(coff.BaseRelocation)).init(gpa); defer { @@ -2006,7 +2026,7 @@ fn writeImportTables(self: *Coff) !void { if (self.idata_section_index == null) return; if (!self.imports_count_dirty) return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const ext = ".dll"; const header = &self.sections.items(.header)[self.idata_section_index.?]; @@ -2154,7 +2174,8 @@ fn writeStrtab(self: *Coff) !void { log.debug("writing strtab from 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + needed_size }); - var buffer = std.ArrayList(u8).init(self.base.allocator); + const gpa = self.base.comp.gpa; + var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); try buffer.ensureTotalCapacityPrecise(needed_size); buffer.appendSliceAssumeCapacity(self.strtab.buffer.items); @@ -2176,7 +2197,7 @@ fn writeDataDirectoriesHeaders(self: *Coff) !void { } fn writeHeader(self: *Coff) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); const writer = buffer.writer(); @@ -2499,7 +2520,7 @@ pub fn getOrPutGlobalPtr(self: *Coff, name: []const u8) !GetOrPutGlobalPtrResult if (self.getGlobalPtr(name)) |ptr| { return GetOrPutGlobalPtrResult{ .found_existing = true, .value_ptr = ptr }; } - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const global_index = try self.allocateGlobal(); const global_name = try gpa.dupe(u8, name); _ = try self.resolver.put(gpa, global_name, global_index); @@ -2530,7 +2551,8 @@ fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !v @memset(header.name[name.len..], 0); return; } - const offset = try self.strtab.insert(self.base.allocator, name); + const gpa = self.base.comp.gpa; + const offset = try self.strtab.insert(gpa, name); const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable; @memset(header.name[name_offset.len..], 0); } @@ -2549,7 +2571,8 @@ fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { @memset(symbol.name[name.len..], 0); return; } - const offset = try self.strtab.insert(self.base.allocator, name); + const gpa = self.base.comp.gpa; + const offset = try self.strtab.insert(gpa, name); @memset(symbol.name[0..4], 0); mem.writeInt(u32, symbol.name[4..8], offset, .little); } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 6f3c59c4e74eff989f240bd67c0ef694caac10c0..ef25c63e3221988b1dc8630b79080331fb96cd60 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -200,26 +200,34 @@ pub const min_text_capacity = padToIdeal(minimum_atom_size); pub const PtrWidth = enum { p32, p64 }; -pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Elf { - assert(options.target.ofmt == .elf); +pub fn open(arena: Allocator, options: link.File.OpenOptions) !*Elf { + if (build_options.only_c) unreachable; + const target = options.comp.root_mod.resolved_target.result; + assert(target.ofmt == .elf); - const self = try createEmpty(allocator, options); + const use_lld = build_options.have_llvm and options.comp.config.use_lld; + const use_llvm = build_options.have_llvm and options.comp.config.use_llvm; + + const self = try createEmpty(arena, options); errdefer self.base.destroy(); + if (use_lld and use_llvm) { + // LLVM emits the object file; LLD links it into the final product. + return self; + } + const is_obj = options.output_mode == .Obj; const is_obj_or_ar = is_obj or (options.output_mode == .Lib and options.link_mode == .Static); - if (options.use_llvm) { - const use_lld = build_options.have_llvm and self.base.options.use_lld; - if (use_lld) return self; - - if (options.module != null) { - self.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{ - sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), - }); - } - } - errdefer if (self.base.intermediary_basename) |path| allocator.free(path); + const sub_path = if (!use_lld) options.emit.sub_path else p: { + // Open a temporary object file, not the final output file because we + // want to link with LLD. + const o_file_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ + options.emit.sub_path, target.ofmt.fileExt(target.cpu.arch), + }); + self.base.intermediary_basename = o_file_path; + break :p o_file_path; + }; self.base.file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = false, @@ -227,24 +235,26 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option .mode = link.determineMode(options), }); + const gpa = options.comp.gpa; + // Index 0 is always a null symbol. - try self.symbols.append(allocator, .{}); + try self.symbols.append(gpa, .{}); // Index 0 is always a null symbol. - try self.symbols_extra.append(allocator, 0); + try self.symbols_extra.append(gpa, 0); // Allocate atom index 0 to null atom - try self.atoms.append(allocator, .{}); + try self.atoms.append(gpa, .{}); // Append null file at index 0 - try self.files.append(allocator, .null); + try self.files.append(gpa, .null); // Append null byte to string tables - try self.shstrtab.append(allocator, 0); - try self.strtab.append(allocator, 0); + try self.shstrtab.append(gpa, 0); + try self.strtab.append(gpa, 0); // There must always be a null shdr in index 0 _ = try self.addSection(.{ .name = "" }); // Append null symbol in output symtab - try self.symtab.append(allocator, null_sym); + try self.symtab.append(gpa, null_sym); if (!is_obj_or_ar) { - try self.dynstrtab.append(allocator, 0); + try self.dynstrtab.append(gpa, 0); // Initialize PT_PHDR program header const p_align: u16 = switch (self.ptr_width) { @@ -283,10 +293,10 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option } if (options.module != null and !options.use_llvm) { - const index = @as(File.Index, @intCast(try self.files.addOne(allocator))); + const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .zig_object = .{ .index = index, - .path = try std.fmt.allocPrint(self.base.allocator, "{s}.o", .{std.fs.path.stem( + .path = try std.fmt.allocPrint(arena, "{s}.o", .{std.fs.path.stem( options.module.?.main_mod.root_src_path, )}), } }); @@ -298,16 +308,16 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option return self; } -pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf { - const ptr_width: PtrWidth = switch (options.target.ptrBitWidth()) { +pub fn createEmpty(arena: Allocator, options: link.File.OpenOptions) !*Elf { + const target = options.comp.root_mod.resolved_target.result; + const ptr_width: PtrWidth = switch (target.ptrBitWidth()) { 0...32 => .p32, 33...64 => .p64, else => return error.UnsupportedELFArchitecture, }; - const self = try gpa.create(Elf); - errdefer gpa.destroy(self); + const self = try arena.create(Elf); - const page_size: u32 = switch (options.target.cpu.arch) { + const page_size: u32 = switch (target.cpu.arch) { .powerpc64le => 0x10000, .sparc64 => 0x2000, else => 0x1000, @@ -321,25 +331,25 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf { self.* = .{ .base = .{ .tag = .elf, - .options = options, - .allocator = gpa, + .comp = options.comp, + .emit = options.emit, .file = null, }, .ptr_width = ptr_width, .page_size = page_size, .default_sym_version = default_sym_version, }; - if (options.use_llvm and options.module != null) { - self.llvm_object = try LlvmObject.create(gpa, options); + if (options.use_llvm and options.comp.config.have_zcu) { + self.llvm_object = try LlvmObject.create(arena, options); } return self; } pub fn deinit(self: *Elf) void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; - if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); + if (self.llvm_object) |llvm_object| llvm_object.deinit(); for (self.files.items(.tags), self.files.items(.data)) |tag, *data| switch (tag) { .null => {}, @@ -496,10 +506,11 @@ fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u64) u64 { /// TODO move to ZigObject pub fn initMetadata(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const ptr_size = self.ptrWidthBytes(); - const ptr_bit_width = self.base.options.target.ptrBitWidth(); - const is_linux = self.base.options.target.os.tag == .linux; + const target = self.base.comp.root_mod.resolved_target.result; + const ptr_bit_width = target.ptrBitWidth(); + const is_linux = target.os.tag == .linux; const zig_object = self.zigObjectPtr().?; const fillSection = struct { @@ -943,7 +954,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node if (use_lld) return; } - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var sub_prog_node = prog_node.start("ELF Flush", 0); sub_prog_node.activate(); defer sub_prog_node.end(); @@ -952,7 +963,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const target = self.base.options.target; + const target = self.base.comp.root_mod.resolved_target.result; const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); const module_obj_path: ?[]const u8 = if (self.base.intermediary_basename) |path| blk: { @@ -1303,7 +1314,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node } pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); defer positionals.deinit(); @@ -1447,7 +1458,7 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const } pub fn flushObject(self: *Elf, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); defer positionals.deinit(); @@ -1524,7 +1535,7 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const target = self.base.options.target; + const target = self.base.comp.root_mod.resolved_target.result; const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); const module_obj_path: ?[]const u8 = if (self.base.intermediary_basename) |path| blk: { @@ -1574,7 +1585,7 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void { } } else { if (!self.isStatic()) { - if (self.base.options.target.dynamic_linker.get()) |path| { + if (target.dynamic_linker.get()) |path| { try argv.append("-dynamic-linker"); try argv.append(path); } @@ -1842,7 +1853,7 @@ fn parseObject(self: *Elf, path: []const u8) ParseError!void { const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const in_file = try std.fs.cwd().openFile(path, .{}); defer in_file.close(); const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); @@ -1862,7 +1873,7 @@ fn parseArchive(self: *Elf, path: []const u8, must_link: bool) ParseError!void { const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const in_file = try std.fs.cwd().openFile(path, .{}); defer in_file.close(); const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); @@ -1888,7 +1899,7 @@ fn parseSharedObject(self: *Elf, lib: SystemLib) ParseError!void { const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const in_file = try std.fs.cwd().openFile(lib.path, .{}); defer in_file.close(); const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); @@ -1910,7 +1921,7 @@ fn parseLdScript(self: *Elf, lib: SystemLib) ParseError!void { const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const in_file = try std.fs.cwd().openFile(lib.path, .{}); defer in_file.close(); const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); @@ -1996,7 +2007,7 @@ fn accessLibPath( link_mode: ?std.builtin.LinkMode, ) !bool { const sep = fs.path.sep_str; - const target = self.base.options.target; + const target = self.base.comp.root_mod.resolved_target.result; test_path.clearRetainingCapacity(); const prefix = if (link_mode != null) "lib" else ""; const suffix = if (link_mode) |mode| switch (mode) { @@ -2190,7 +2201,7 @@ fn claimUnresolvedObject(self: *Elf) void { /// This is also the point where we will report undefined symbols for any /// alloc sections. fn scanRelocs(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var undefs = std.AutoHashMap(Symbol.Index, std.ArrayList(Atom.Index)).init(gpa); defer { @@ -2293,7 +2304,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; const have_dynamic_linker = self.base.options.link_libc and self.base.options.link_mode == .Dynamic and is_exe_or_dyn_lib; - const target = self.base.options.target; + const target = self.base.comp.root_mod.resolved_target.result; const gc_sections = self.base.options.gc_sections orelse !is_obj; const stack_size = self.base.options.stack_size_override orelse 16777216; const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os; @@ -2374,7 +2385,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v man.hash.addBytes(libc_installation.crt_dir.?); } if (have_dynamic_linker) { - man.hash.addOptionalBytes(self.base.options.target.dynamic_linker.get()); + man.hash.addOptionalBytes(target.dynamic_linker.get()); } } man.hash.addOptionalBytes(self.base.options.soname); @@ -2687,7 +2698,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v } if (have_dynamic_linker) { - if (self.base.options.target.dynamic_linker.get()) |dynamic_linker| { + if (target.dynamic_linker.get()) |dynamic_linker| { try argv.append("-dynamic-linker"); try argv.append(dynamic_linker); } @@ -2937,7 +2948,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v } fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void { - const target_endian = self.base.options.target.cpu.arch.endian(); + const target = self.base.comp.root_mod.resolved_target.result; + const target_endian = target.cpu.arch.endian(); switch (self.ptr_width) { .p32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @as(u32, @intCast(addr)), target_endian), .p64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), addr, target_endian), @@ -2945,8 +2957,9 @@ fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) } fn writeShdrTable(self: *Elf) !void { - const gpa = self.base.allocator; - const target_endian = self.base.options.target.cpu.arch.endian(); + const gpa = self.base.comp.gpa; + const target = self.base.comp.root_mod.resolved_target.result; + const target_endian = target.cpu.arch.endian(); const foreign_endian = target_endian != builtin.cpu.arch.endian(); const shsize: u64 = switch (self.ptr_width) { .p32 => @sizeOf(elf.Elf32_Shdr), @@ -3001,8 +3014,9 @@ fn writeShdrTable(self: *Elf) !void { } fn writePhdrTable(self: *Elf) !void { - const gpa = self.base.allocator; - const target_endian = self.base.options.target.cpu.arch.endian(); + const gpa = self.base.comp.gpa; + const target = self.base.comp.root_mod.resolved_target.result; + const target_endian = target.cpu.arch.endian(); const foreign_endian = target_endian != builtin.cpu.arch.endian(); const phdr_table = &self.phdrs.items[self.phdr_table_index.?]; @@ -3054,7 +3068,8 @@ fn writeElfHeader(self: *Elf) !void { }; index += 1; - const endian = self.base.options.target.cpu.arch.endian(); + const target = self.base.comp.root_mod.resolved_target.result; + const endian = target.cpu.arch.endian(); hdr_buf[index] = switch (endian) { .little => elf.ELFDATA2LSB, .big => elf.ELFDATA2MSB, @@ -3083,7 +3098,7 @@ fn writeElfHeader(self: *Elf) !void { mem.writeInt(u16, hdr_buf[index..][0..2], @intFromEnum(elf_type), endian); index += 2; - const machine = self.base.options.target.cpu.arch.toElfMachine(); + const machine = target.cpu.arch.toElfMachine(); mem.writeInt(u16, hdr_buf[index..][0..2], @intFromEnum(machine), endian); index += 2; @@ -3248,7 +3263,7 @@ fn addLinkerDefinedSymbols(self: *Elf) !void { for (self.shdrs.items) |shdr| { if (self.getStartStopBasename(shdr)) |name| { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.start_stop_indexes.ensureUnusedCapacity(gpa, 2); const start = try std.fmt.allocPrintZ(gpa, "__start_{s}", .{name}); @@ -3394,6 +3409,7 @@ fn initOutputSections(self: *Elf) !void { } fn initSyntheticSections(self: *Elf) !void { + const target = self.base.comp.root_mod.resolved_target.result; const ptr_size = self.ptrWidthBytes(); const needs_eh_frame = for (self.objects.items) |index| { @@ -3503,7 +3519,7 @@ fn initSyntheticSections(self: *Elf) !void { // a segfault in the dynamic linker trying to load a binary that is static // and doesn't contain .dynamic section. if (self.isStatic() and !self.base.options.pie) break :blk false; - break :blk self.base.options.target.dynamic_linker.get() != null; + break :blk target.dynamic_linker.get() != null; }; if (needs_interp) { self.interp_section_index = try self.addSection(.{ @@ -3613,7 +3629,7 @@ fn initSectionsObject(self: *Elf) !void { } fn initComdatGroups(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; for (self.objects.items) |index| { const object = self.file(index).?.object; @@ -3732,7 +3748,7 @@ fn initSpecialPhdrs(self: *Elf) !void { /// Ties are broken by the file prority which corresponds to the inclusion of input sections in this output section /// we are about to sort. fn sortInitFini(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const Entry = struct { priority: i32, @@ -3872,7 +3888,7 @@ fn sortPhdrs(self: *Elf) error{OutOfMemory}!void { } }; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var entries = try std.ArrayList(Entry).initCapacity(gpa, self.phdrs.items.len); defer entries.deinit(); for (0..self.phdrs.items.len) |phndx| { @@ -3977,7 +3993,7 @@ fn sortShdrs(self: *Elf) !void { } }; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var entries = try std.ArrayList(Entry).initCapacity(gpa, self.shdrs.items.len); defer entries.deinit(); for (0..self.shdrs.items.len) |shndx| { @@ -4004,7 +4020,7 @@ fn sortShdrs(self: *Elf) !void { } fn resetShdrIndexes(self: *Elf, backlinks: []const u16) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; for (&[_]*?u16{ &self.eh_frame_section_index, @@ -4187,6 +4203,7 @@ fn resetShdrIndexes(self: *Elf, backlinks: []const u16) !void { } fn updateSectionSizes(self: *Elf) !void { + const target = self.base.comp.root_mod.resolved_target.result; for (self.output_sections.keys(), self.output_sections.values()) |shndx, atom_list| { const shdr = &self.shdrs.items[shndx]; for (atom_list.items) |atom_index| { @@ -4244,7 +4261,7 @@ fn updateSectionSizes(self: *Elf) !void { } if (self.interp_section_index) |index| { - self.shdrs.items[index].sh_size = self.base.options.target.dynamic_linker.get().?.len + 1; + self.shdrs.items[index].sh_size = target.dynamic_linker.get().?.len + 1; } if (self.hash_section_index) |index| { @@ -4453,7 +4470,7 @@ fn allocateAllocSections(self: *Elf) error{OutOfMemory}!void { // as we are more interested in quick turnaround and compatibility // with `findFreeSpace` mechanics than anything else. const Cover = std.ArrayList(u16); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var covers: [max_number_of_object_segments]Cover = undefined; for (&covers) |*cover| { cover.* = Cover.init(gpa); @@ -4691,7 +4708,7 @@ fn allocateAtoms(self: *Elf) void { } fn writeAtoms(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var undefs = std.AutoHashMap(Symbol.Index, std.ArrayList(Atom.Index)).init(gpa); defer { @@ -4779,7 +4796,7 @@ fn writeAtoms(self: *Elf) !void { } fn writeAtomsObject(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; // TODO iterate over `output_sections` directly for (self.shdrs.items, 0..) |shdr, shndx| { @@ -4852,7 +4869,7 @@ fn updateSymtabSize(self: *Elf) !void { var nglobals: u32 = 0; var strsize: u32 = 0; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var files = std.ArrayList(File.Index).init(gpa); defer files.deinit(); try files.ensureTotalCapacityPrecise(self.objects.items.len + self.shared_objects.items.len + 2); @@ -4935,11 +4952,12 @@ fn updateSymtabSize(self: *Elf) !void { } fn writeSyntheticSections(self: *Elf) !void { - const gpa = self.base.allocator; + const target = self.base.comp.root_mod.resolved_target.result; + const gpa = self.base.comp.gpa; if (self.interp_section_index) |shndx| { var buffer: [256]u8 = undefined; - const interp = self.base.options.target.dynamic_linker.get().?; + const interp = target.dynamic_linker.get().?; @memcpy(buffer[0..interp.len], interp); buffer[interp.len] = 0; const contents = buffer[0 .. interp.len + 1]; @@ -5065,7 +5083,7 @@ fn writeSyntheticSections(self: *Elf) !void { } fn writeSyntheticSectionsObject(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; for (self.output_rela_sections.values()) |sec| { if (sec.atom_list.items.len == 0) continue; @@ -5135,7 +5153,7 @@ fn writeSyntheticSectionsObject(self: *Elf) !void { } fn writeComdatGroups(self: *Elf) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; for (self.comdat_group_sections.items) |cgs| { const shdr = self.shdrs.items[cgs.shndx]; const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; @@ -5160,7 +5178,8 @@ fn writeShStrtab(self: *Elf) !void { } fn writeSymtab(self: *Elf) !void { - const gpa = self.base.allocator; + const target = self.base.comp.root_mod.resolved_target.result; + const gpa = self.base.comp.gpa; const symtab_shdr = self.shdrs.items[self.symtab_section_index.?]; const strtab_shdr = self.shdrs.items[self.strtab_section_index.?]; const sym_size: u64 = switch (self.ptr_width) { @@ -5220,7 +5239,7 @@ fn writeSymtab(self: *Elf) !void { self.plt_got.writeSymtab(self); } - const foreign_endian = self.base.options.target.cpu.arch.endian() != builtin.cpu.arch.endian(); + const foreign_endian = target.cpu.arch.endian() != builtin.cpu.arch.endian(); switch (self.ptr_width) { .p32 => { const buf = try gpa.alloc(elf.Elf32_Sym, self.symtab.items.len); @@ -5299,7 +5318,8 @@ fn ptrWidthBytes(self: Elf) u8 { /// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes /// in a 32-bit ELF file. pub fn archPtrWidthBytes(self: Elf) u8 { - return @as(u8, @intCast(@divExact(self.base.options.target.ptrBitWidth(), 8))); + const target = self.base.comp.root_mod.resolved_target.result; + return @intCast(@divExact(target.ptrBitWidth(), 8)); } fn phdrTo32(phdr: elf.Elf64_Phdr) elf.Elf32_Phdr { @@ -5694,7 +5714,7 @@ pub const AddSectionOpts = struct { }; pub fn addSection(self: *Elf, opts: AddSectionOpts) !u16 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const index = @as(u16, @intCast(self.shdrs.items.len)); const shdr = try self.shdrs.addOne(gpa); shdr.* = .{ @@ -5887,7 +5907,7 @@ const GetOrPutGlobalResult = struct { }; pub fn getOrPutGlobal(self: *Elf, name: []const u8) !GetOrPutGlobalResult { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const name_off = try self.strings.insert(gpa, name); const gop = try self.resolver.getOrPut(gpa, name_off); if (!gop.found_existing) { @@ -5923,7 +5943,7 @@ const GetOrCreateComdatGroupOwnerResult = struct { }; pub fn getOrCreateComdatGroupOwner(self: *Elf, name: [:0]const u8) !GetOrCreateComdatGroupOwnerResult { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const off = try self.strings.insert(gpa, name); const gop = try self.comdat_groups_table.getOrPut(gpa, off); if (!gop.found_existing) { @@ -6039,7 +6059,7 @@ pub fn insertDynString(self: *Elf, name: []const u8) error{OutOfMemory}!u32 { } fn reportUndefinedSymbols(self: *Elf, undefs: anytype) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const max_notes = 4; try self.misc_errors.ensureUnusedCapacity(gpa, undefs.count()); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 61446b3300d62273005a251c5f0ef51fe69da848..f61e78c2331aa6f7d1b04ef454163d5cd885c834 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -143,14 +143,23 @@ tlv_table: TlvSymbolTable = .{}, /// Hot-code swapping state. hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, -pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { - assert(options.target.ofmt == .macho); +darwin_sdk_layout: ?SdkLayout, - if (options.emit == null) { - return createEmpty(allocator, options); - } +/// The filesystem layout of darwin SDK elements. +pub const SdkLayout = enum { + /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }. + sdk, + /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, }. + vendored, +}; - const emit = options.emit.?; +pub fn open(arena: Allocator, options: link.File.OpenOptions) !*MachO { + if (build_options.only_c) unreachable; + const target = options.comp.root_mod.resolved_target.result; + assert(target.ofmt == .macho); + + const gpa = options.comp.gpa; + const emit = options.emit; const mode: Mode = mode: { if (options.use_llvm or options.module == null or options.cache_mode == .whole) break :mode .zld; @@ -160,17 +169,16 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { if (options.module == null) { // No point in opening a file, we would not write anything to it. // Initialize with empty. - return createEmpty(allocator, options); + return createEmpty(arena, options); } // Open a temporary object file, not the final output file because we // want to link with LLD. - break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ - emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), + break :blk try std.fmt.allocPrint(arena, "{s}{s}", .{ + emit.sub_path, target.ofmt.fileExt(target.cpu.arch), }); } else emit.sub_path; - errdefer if (mode == .zld) allocator.free(sub_path); - const self = try createEmpty(allocator, options); + const self = try createEmpty(arena, options); errdefer self.base.destroy(); if (mode == .zld) { @@ -186,7 +194,6 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { .read = true, .mode = link.determineMode(options), }); - errdefer file.close(); self.base.file = file; if (!options.strip and options.module != null) { @@ -194,11 +201,10 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { log.debug("creating {s}.dSYM bundle", .{sub_path}); const d_sym_path = try std.fmt.allocPrint( - allocator, + arena, "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF", .{sub_path}, ); - defer allocator.free(d_sym_path); var d_sym_bundle = try emit.directory.handle.makeOpenPath(d_sym_path, .{}); defer d_sym_bundle.close(); @@ -209,21 +215,21 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { }); self.d_sym = .{ - .allocator = allocator, - .dwarf = link.File.Dwarf.init(allocator, &self.base, .dwarf32), + .allocator = gpa, + .dwarf = link.File.Dwarf.init(gpa, &self.base, .dwarf32), .file = d_sym_file, }; } // Index 0 is always a null symbol. - try self.locals.append(allocator, .{ + try self.locals.append(gpa, .{ .n_strx = 0, .n_type = 0, .n_sect = 0, .n_desc = 0, .n_value = 0, }); - try self.strtab.buffer.append(allocator, 0); + try self.strtab.buffer.append(gpa, 0); try self.populateMissingMetadata(); @@ -234,15 +240,14 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { return self; } -pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { - const self = try gpa.create(MachO); - errdefer gpa.destroy(self); +pub fn createEmpty(arena: Allocator, options: link.File.OpenOptions) !*MachO { + const self = try arena.create(MachO); self.* = .{ .base = .{ .tag = .macho, - .options = options, - .allocator = gpa, + .comp = options.comp, + .emit = options.emit, .file = null, }, .mode = if (options.use_llvm or options.module == null or options.cache_mode == .whole) @@ -252,7 +257,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { }; if (options.use_llvm and options.module != null) { - self.llvm_object = try LlvmObject.create(gpa, options); + self.llvm_object = try LlvmObject.create(arena, options); } log.debug("selected linker mode '{s}'", .{@tagName(self.mode)}); @@ -261,20 +266,15 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { } pub fn flush(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void { - if (self.base.options.emit == null) { - if (self.llvm_object) |llvm_object| { - try llvm_object.flushModule(comp, prog_node); - } - return; - } + const gpa = self.base.comp.gpa; if (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Static) { if (build_options.have_llvm) { return self.base.linkAsArchive(comp, prog_node); } else { - try self.misc_errors.ensureUnusedCapacity(self.base.allocator, 1); + try self.misc_errors.ensureUnusedCapacity(gpa, 1); self.misc_errors.appendAssumeCapacity(.{ - .msg = try self.base.allocator.dupe(u8, "TODO: non-LLVM archiver for MachO object files"), + .msg = try gpa.dupe(u8, "TODO: non-LLVM archiver for MachO object files"), }); return error.FlushFailure; } @@ -294,7 +294,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No return try llvm_object.flushModule(comp, prog_node); } - var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); + const gpa = self.base.comp.gpa; + var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); @@ -391,7 +392,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No if (cache_miss) { for (self.dylibs.items) |*dylib| { - dylib.deinit(self.base.allocator); + dylib.deinit(gpa); } self.dylibs.clearRetainingCapacity(); self.dylibs_map.clearRetainingCapacity(); @@ -403,7 +404,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No const in_file = try std.fs.cwd().openFile(path, .{}); defer in_file.close(); - var parse_ctx = ParseErrorCtx.init(self.base.allocator); + var parse_ctx = ParseErrorCtx.init(gpa); defer parse_ctx.deinit(); self.parseLibrary( @@ -470,7 +471,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No const section = self.sections.get(sym.n_sect - 1).header; const file_offset = section.offset + sym.n_value - section.addr; - var code = std.ArrayList(u8).init(self.base.allocator); + var code = std.ArrayList(u8).init(gpa); defer code.deinit(); try code.resize(math.cast(usize, atom.size) orelse return error.Overflow); @@ -518,12 +519,12 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No var codesig = CodeSignature.init(getPageSize(self.base.options.target.cpu.arch)); codesig.code_directory.ident = self.base.options.emit.?.sub_path; if (self.base.options.entitlements) |path| { - try codesig.addEntitlements(self.base.allocator, path); + try codesig.addEntitlements(gpa, path); } try self.writeCodeSignaturePadding(&codesig); break :blk codesig; } else null; - defer if (codesig) |*csig| csig.deinit(self.base.allocator); + defer if (codesig) |*csig| csig.deinit(gpa); // Write load commands var lc_buffer = std.ArrayList(u8).init(arena); @@ -555,12 +556,12 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No }); }, .Lib => if (self.base.options.link_mode == .Dynamic) { - try load_commands.writeDylibIdLC(self.base.allocator, &self.base.options, lc_writer); + try load_commands.writeDylibIdLC(gpa, &self.base.options, lc_writer); }, else => {}, } - try load_commands.writeRpathLCs(self.base.allocator, &self.base.options, lc_writer); + try load_commands.writeRpathLCs(gpa, &self.base.options, lc_writer); try lc_writer.writeStruct(macho.source_version_command{ .version = 0, }); @@ -644,7 +645,8 @@ pub fn resolveLibSystem( search_dirs: []const []const u8, out_libs: anytype, ) !void { - var tmp_arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); + const gpa = self.base.comp.gpa; + var tmp_arena_allocator = std.heap.ArenaAllocator.init(gpa); defer tmp_arena_allocator.deinit(); const tmp_arena = tmp_arena_allocator.allocator(); @@ -775,7 +777,7 @@ fn parseObject( const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mtime: u64 = mtime: { const stat = file.stat() catch break :mtime 0; break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); @@ -868,7 +870,7 @@ pub fn parseFatLibrary( cpu_arch: std.Target.Cpu.Arch, ctx: *ParseErrorCtx, ) ParseError!u64 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const fat_archs = try fat.parseArchs(gpa, file); defer gpa.free(fat_archs); @@ -892,7 +894,7 @@ fn parseArchive( must_link: bool, ctx: *ParseErrorCtx, ) ParseError!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; // We take ownership of the file so that we can store it for the duration of symbol resolution. // TODO we shouldn't need to do that and could pre-parse the archive like we do for zld/ELF? @@ -973,7 +975,7 @@ fn parseDylib( dylib_options: DylibOpts, ctx: *ParseErrorCtx, ) ParseError!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const file_stat = try file.stat(); const file_size = math.cast(usize, file_stat.size - offset) orelse return error.Overflow; @@ -1019,7 +1021,7 @@ fn parseLibStub( dylib_options: DylibOpts, ctx: *ParseErrorCtx, ) ParseError!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var lib_stub = try LibStub.loadFromFile(gpa, file); defer lib_stub.deinit(); @@ -1072,7 +1074,7 @@ fn addDylib(self: *MachO, dylib: Dylib, dylib_options: DylibOpts, ctx: *ParseErr } } - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const gop = try self.dylibs_map.getOrPut(gpa, dylib.id.?.name); if (gop.found_existing) return error.DylibAlreadyExists; @@ -1098,7 +1100,7 @@ pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype) !void { // 2) afterwards, we parse dependents of the included dylibs // TODO this should not be performed if the user specifies `-flat_namespace` flag. // See ld64 manpages. - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; while (dependent_libs.readItem()) |dep_id| { defer dep_id.id.deinit(gpa); @@ -1162,7 +1164,8 @@ pub fn writeAtom(self: *MachO, atom_index: Atom.Index, code: []u8) !void { log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); // Gather relocs which can be resolved. - var relocs = std.ArrayList(*Relocation).init(self.base.allocator); + const gpa = self.base.comp.gpa; + var relocs = std.ArrayList(*Relocation).init(gpa); defer relocs.deinit(); if (self.relocs.getPtr(atom_index)) |rels| { @@ -1237,7 +1240,7 @@ fn writeOffsetTableEntry(self: *MachO, index: usize) !void { fn writeStubHelperPreamble(self: *MachO) !void { if (self.stub_helper_preamble_allocated) return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const cpu_arch = self.base.options.target.cpu.arch; const size = stubs.stubHelperPreambleSize(cpu_arch); @@ -1290,7 +1293,7 @@ fn writeStubTableEntry(self: *MachO, index: usize) !void { self.stub_table_count_dirty = false; } - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const stubs_header = self.sections.items(.header)[stubs_sect_id]; const stub_helper_header = self.sections.items(.header)[stub_helper_sect_id]; @@ -1469,7 +1472,7 @@ const CreateAtomOpts = struct { }; pub fn createAtom(self: *MachO, sym_index: u32, opts: CreateAtomOpts) !Atom.Index { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const index = @as(Atom.Index, @intCast(self.atoms.items.len)); const atom = try self.atoms.addOne(gpa); atom.* = .{}; @@ -1481,7 +1484,7 @@ pub fn createAtom(self: *MachO, sym_index: u32, opts: CreateAtomOpts) !Atom.Inde } pub fn createTentativeDefAtoms(self: *MachO) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; for (self.globals.items) |global| { const sym = self.getSymbolPtr(global); @@ -1536,7 +1539,8 @@ pub fn createDyldPrivateAtom(self: *MachO) !void { .size = @sizeOf(u64), .alignment = .@"8", }); - try self.atom_by_index_table.putNoClobber(self.base.allocator, sym_index, atom_index); + const gpa = self.base.comp.gpa; + try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index); if (self.data_section_index == null) { self.data_section_index = try self.initSection("__DATA", "__data", .{}); @@ -1560,7 +1564,7 @@ pub fn createDyldPrivateAtom(self: *MachO) !void { } fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: SymbolWithLoc) !Atom.Index { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const size = 3 * @sizeOf(u64); const required_alignment: Alignment = .@"1"; const sym_index = try self.allocateSymbol(); @@ -1595,7 +1599,7 @@ fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: S pub fn createMhExecuteHeaderSymbol(self: *MachO) !void { if (self.base.options.output_mode != .Exe) return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const sym_index = try self.allocateSymbol(); const sym_loc = SymbolWithLoc{ .sym_index = sym_index }; const sym = self.getSymbolPtr(sym_loc); @@ -1622,7 +1626,7 @@ pub fn createDsoHandleSymbol(self: *MachO) !void { const global = self.getGlobalPtr("___dso_handle") orelse return; if (!self.getSymbol(global.*).undf()) return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const sym_index = try self.allocateSymbol(); const sym_loc = SymbolWithLoc{ .sym_index = sym_index }; const sym = self.getSymbolPtr(sym_loc); @@ -1686,7 +1690,7 @@ pub fn resolveSymbols(self: *MachO) !void { } fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const sym = self.getSymbol(current); const sym_name = self.getSymbolName(current); @@ -1800,7 +1804,7 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u32) !void { fn resolveSymbolsInArchives(self: *MachO) !void { if (self.archives.items.len == 0) return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var next_sym: usize = 0; loop: while (next_sym < self.unresolved.count()) { const global = self.globals.items[self.unresolved.keys()[next_sym]]; @@ -1829,7 +1833,7 @@ fn resolveSymbolsInArchives(self: *MachO) !void { fn resolveSymbolsInDylibs(self: *MachO) !void { if (self.dylibs.items.len == 0) return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var next_sym: usize = 0; loop: while (next_sym < self.unresolved.count()) { const global_index = self.unresolved.keys()[next_sym]; @@ -1899,6 +1903,7 @@ fn resolveSymbolsAtLoading(self: *MachO) !void { } fn resolveBoundarySymbols(self: *MachO) !void { + const gpa = self.base.comp.gpa; var next_sym: usize = 0; while (next_sym < self.unresolved.count()) { const global_index = self.unresolved.keys()[next_sym]; @@ -1909,7 +1914,7 @@ fn resolveBoundarySymbols(self: *MachO) !void { const sym_loc = SymbolWithLoc{ .sym_index = sym_index }; const sym = self.getSymbolPtr(sym_loc); sym.* = .{ - .n_strx = try self.strtab.insert(self.base.allocator, self.getSymbolName(global.*)), + .n_strx = try self.strtab.insert(gpa, self.getSymbolName(global.*)), .n_type = macho.N_SECT | macho.N_EXT, .n_sect = 0, .n_desc = N_BOUNDARY, @@ -1929,9 +1934,9 @@ fn resolveBoundarySymbols(self: *MachO) !void { } pub fn deinit(self: *MachO) void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; - if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); + if (self.llvm_object) |llvm_object| llvm_object.deinit(); if (self.d_sym) |*d_sym| { d_sym.deinit(); @@ -2032,7 +2037,7 @@ pub fn deinit(self: *MachO) void { } fn freeAtom(self: *MachO, atom_index: Atom.Index) void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; log.debug("freeAtom {d}", .{atom_index}); // Remove any relocs and base relocs associated with this Atom @@ -2124,7 +2129,8 @@ fn growAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: } pub fn allocateSymbol(self: *MachO) !u32 { - try self.locals.ensureUnusedCapacity(self.base.allocator, 1); + const gpa = self.base.comp.gpa; + try self.locals.ensureUnusedCapacity(gpa, 1); const index = blk: { if (self.locals_free_list.popOrNull()) |index| { @@ -2150,7 +2156,8 @@ pub fn allocateSymbol(self: *MachO) !u32 { } fn allocateGlobal(self: *MachO) !u32 { - try self.globals.ensureUnusedCapacity(self.base.allocator, 1); + const gpa = self.base.comp.gpa; + try self.globals.ensureUnusedCapacity(gpa, 1); const index = blk: { if (self.globals_free_list.popOrNull()) |index| { @@ -2171,7 +2178,8 @@ fn allocateGlobal(self: *MachO) !u32 { pub fn addGotEntry(self: *MachO, target: SymbolWithLoc) !void { if (self.got_table.lookup.contains(target)) return; - const got_index = try self.got_table.allocateEntry(self.base.allocator, target); + const gpa = self.base.comp.gpa; + const got_index = try self.got_table.allocateEntry(gpa, target); if (self.got_section_index == null) { self.got_section_index = try self.initSection("__DATA_CONST", "__got", .{ .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, @@ -2186,7 +2194,8 @@ pub fn addGotEntry(self: *MachO, target: SymbolWithLoc) !void { pub fn addStubEntry(self: *MachO, target: SymbolWithLoc) !void { if (self.stub_table.lookup.contains(target)) return; - const stub_index = try self.stub_table.allocateEntry(self.base.allocator, target); + const gpa = self.base.comp.gpa; + const stub_index = try self.stub_table.allocateEntry(gpa, target); if (self.stubs_section_index == null) { self.stubs_section_index = try self.initSection("__TEXT", "__stubs", .{ .flags = macho.S_SYMBOL_STUBS | @@ -2212,7 +2221,8 @@ pub fn addStubEntry(self: *MachO, target: SymbolWithLoc) !void { pub fn addTlvPtrEntry(self: *MachO, target: SymbolWithLoc) !void { if (self.tlv_ptr_table.lookup.contains(target)) return; - _ = try self.tlv_ptr_table.allocateEntry(self.base.allocator, target); + const gpa = self.base.comp.gpa; + _ = try self.tlv_ptr_table.allocateEntry(gpa, target); if (self.tlv_ptr_section_index == null) { self.tlv_ptr_section_index = try self.initSection("__DATA", "__thread_ptrs", .{ .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS, @@ -2236,7 +2246,8 @@ pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: self.freeUnnamedConsts(decl_index); Atom.freeRelocations(self, atom_index); - var code_buffer = std.ArrayList(u8).init(self.base.allocator); + const gpa = self.base.comp.gpa; + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); var decl_state = if (self.d_sym) |*d_sym| @@ -2279,7 +2290,7 @@ pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: } pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: InternPool.DeclIndex) !u32 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const gop = try self.unnamed_const_atoms.getOrPut(gpa, decl_index); if (!gop.found_existing) { @@ -2318,7 +2329,7 @@ fn lowerConst( sect_id: u8, src_loc: Module.SrcLoc, ) !LowerConstResult { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); @@ -2366,6 +2377,7 @@ pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) const tracy = trace(@src()); defer tracy.end(); + const gpa = self.base.comp.gpa; const decl = mod.declPtr(decl_index); if (decl.val.getExternFunc(mod)) |_| { @@ -2375,8 +2387,8 @@ pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) if (decl.isExtern(mod)) { // TODO make this part of getGlobalSymbol const name = mod.intern_pool.stringToSlice(decl.name); - const sym_name = try std.fmt.allocPrint(self.base.allocator, "_{s}", .{name}); - defer self.base.allocator.free(sym_name); + const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name}); + defer gpa.free(sym_name); _ = try self.addUndefined(sym_name, .{ .add_got = true }); return; } @@ -2391,7 +2403,7 @@ pub fn updateDecl(self: *MachO, mod: *Module, decl_index: InternPool.DeclIndex) const sym_index = self.getAtom(atom_index).getSymbolIndex().?; Atom.freeRelocations(self, atom_index); - var code_buffer = std.ArrayList(u8).init(self.base.allocator); + var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); var decl_state: ?Dwarf.DeclState = if (self.d_sym) |*d_sym| @@ -2449,7 +2461,7 @@ fn updateLazySymbolAtom( atom_index: Atom.Index, section_index: u8, ) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; var required_alignment: Alignment = .none; @@ -2515,7 +2527,8 @@ fn updateLazySymbolAtom( pub fn getOrCreateAtomForLazySymbol(self: *MachO, sym: File.LazySymbol) !Atom.Index { const mod = self.base.options.module.?; - const gop = try self.lazy_syms.getOrPut(self.base.allocator, sym.getDecl(mod)); + const gpa = self.base.comp.gpa; + const gop = try self.lazy_syms.getOrPut(gpa, sym.getDecl(mod)); errdefer _ = if (!gop.found_existing) self.lazy_syms.pop(); if (!gop.found_existing) gop.value_ptr.* = .{}; const metadata: struct { atom: *Atom.Index, state: *LazySymbolMetadata.State } = switch (sym.kind) { @@ -2529,7 +2542,7 @@ pub fn getOrCreateAtomForLazySymbol(self: *MachO, sym: File.LazySymbol) !Atom.In .unused => { const sym_index = try self.allocateSymbol(); metadata.atom.* = try self.createAtom(sym_index, .{}); - try self.atom_by_index_table.putNoClobber(self.base.allocator, sym_index, metadata.atom.*); + try self.atom_by_index_table.putNoClobber(gpa, sym_index, metadata.atom.*); }, .pending_flush => return metadata.atom.*, .flushed => {}, @@ -2556,7 +2569,7 @@ fn updateThreadlocalVariable(self: *MachO, module: *Module, decl_index: InternPo const init_sym_index = init_atom.getSymbolIndex().?; Atom.freeRelocations(self, init_atom_index); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var code_buffer = std.ArrayList(u8).init(gpa); defer code_buffer.deinit(); @@ -2640,11 +2653,12 @@ fn updateThreadlocalVariable(self: *MachO, module: *Module, decl_index: InternPo } pub fn getOrCreateAtomForDecl(self: *MachO, decl_index: InternPool.DeclIndex) !Atom.Index { - const gop = try self.decls.getOrPut(self.base.allocator, decl_index); + const gpa = self.base.comp.gpa; + const gop = try self.decls.getOrPut(gpa, decl_index); if (!gop.found_existing) { const sym_index = try self.allocateSymbol(); const atom_index = try self.createAtom(sym_index, .{}); - try self.atom_by_index_table.putNoClobber(self.base.allocator, sym_index, atom_index); + try self.atom_by_index_table.putNoClobber(gpa, sym_index, atom_index); gop.value_ptr.* = .{ .atom = atom_index, .section = self.getDeclOutputSection(decl_index), @@ -2694,7 +2708,7 @@ fn getDeclOutputSection(self: *MachO, decl_index: InternPool.DeclIndex) u8 { } fn updateDeclCode(self: *MachO, decl_index: InternPool.DeclIndex, code: []u8) !u64 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); @@ -2787,7 +2801,7 @@ pub fn updateExports( const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const metadata = switch (exported) { .decl_index => |decl_index| blk: { @@ -2912,7 +2926,7 @@ pub fn deleteDeclExport( if (self.llvm_object) |_| return; const metadata = self.decls.getPtr(decl_index) orelse return; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const exp_name = try std.fmt.allocPrint(gpa, "_{s}", .{mod.intern_pool.stringToSlice(name)}); defer gpa.free(exp_name); @@ -2941,7 +2955,7 @@ pub fn deleteDeclExport( } fn freeUnnamedConsts(self: *MachO, decl_index: InternPool.DeclIndex) void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const unnamed_consts = self.unnamed_const_atoms.getPtr(decl_index) orelse return; for (unnamed_consts.items) |atom| { self.freeAtom(atom); @@ -2951,6 +2965,7 @@ fn freeUnnamedConsts(self: *MachO, decl_index: InternPool.DeclIndex) void { pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void { if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index); + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); @@ -2960,7 +2975,7 @@ pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void { var kv = const_kv; self.freeAtom(kv.value.atom); self.freeUnnamedConsts(decl_index); - kv.value.exports.deinit(self.base.allocator); + kv.value.exports.deinit(gpa); } if (self.d_sym) |*d_sym| { @@ -2993,7 +3008,7 @@ pub fn lowerAnonDecl( explicit_alignment: InternPool.Alignment, src_loc: Module.SrcLoc, ) !codegen.Result { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const mod = self.base.options.module.?; const ty = Type.fromInterned(mod.intern_pool.typeOf(decl_val)); const decl_alignment = switch (explicit_alignment) { @@ -3060,7 +3075,7 @@ pub fn getAnonDeclVAddr(self: *MachO, decl_val: InternPool.Index, reloc_info: li fn populateMissingMetadata(self: *MachO) !void { assert(self.mode == .incremental); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const cpu_arch = self.base.options.target.cpu.arch; const pagezero_vmsize = self.calcPagezeroSize(); @@ -3228,7 +3243,8 @@ const InitSectionOpts = struct { pub fn initSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: InitSectionOpts) !u8 { log.debug("creating section '{s},{s}'", .{ segname, sectname }); const index = @as(u8, @intCast(self.sections.slice().len)); - try self.sections.append(self.base.allocator, .{ + const gpa = self.base.comp.gpa; + try self.sections.append(gpa, .{ .segment_index = undefined, // Segments will be created automatically later down the pipeline .header = .{ .sectname = makeStaticString(sectname), @@ -3248,7 +3264,7 @@ fn allocateSection(self: *MachO, segname: []const u8, sectname: []const u8, opts flags: u32 = macho.S_REGULAR, reserved2: u32 = 0, }) !u8 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const page_size = getPageSize(self.base.options.target.cpu.arch); // In incremental context, we create one section per segment pairing. This way, // we can move the segment in raw file as we please. @@ -3521,7 +3537,7 @@ fn allocateAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignm pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 { _ = lib_name; - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const sym_name = try std.fmt.allocPrint(gpa, "_{s}", .{name}); defer gpa.free(sym_name); return self.addUndefined(sym_name, .{ .add_stub = true }); @@ -3582,7 +3598,7 @@ pub fn writeLinkeditSegmentData(self: *MachO) !void { } fn collectRebaseDataFromTableSection(self: *MachO, sect_id: u8, rebase: *Rebase, table: anytype) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const header = self.sections.items(.header)[sect_id]; const segment_index = self.sections.items(.segment_index)[sect_id]; const segment = self.segments.items[segment_index]; @@ -3605,7 +3621,7 @@ fn collectRebaseDataFromTableSection(self: *MachO, sect_id: u8, rebase: *Rebase, } fn collectRebaseData(self: *MachO, rebase: *Rebase) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const slice = self.sections.slice(); for (self.rebases.keys(), 0..) |atom_index, i| { @@ -3715,7 +3731,7 @@ fn collectRebaseData(self: *MachO, rebase: *Rebase) !void { } fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, table: anytype) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const header = self.sections.items(.header)[sect_id]; const segment_index = self.sections.items(.segment_index)[sect_id]; const segment = self.segments.items[segment_index]; @@ -3746,7 +3762,7 @@ fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, tab } fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const slice = self.sections.slice(); for (raw_bindings.keys(), 0..) |atom_index, i| { @@ -3885,12 +3901,13 @@ fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void { fn collectLazyBindData(self: *MachO, bind: anytype) !void { const sect_id = self.la_symbol_ptr_section_index orelse return; + const gpa = self.base.comp.gpa; try self.collectBindDataFromTableSection(sect_id, bind, self.stub_table); - try bind.finalize(self.base.allocator, self); + try bind.finalize(gpa, self); } fn collectExportData(self: *MachO, trie: *Trie) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; // TODO handle macho.EXPORT_SYMBOL_FLAGS_REEXPORT and macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER. log.debug("generating export trie", .{}); @@ -3922,7 +3939,7 @@ fn writeDyldInfoData(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var rebase = Rebase{}; defer rebase.deinit(gpa); @@ -4046,7 +4063,7 @@ fn addSymbolToFunctionStarts(self: *MachO, sym_loc: SymbolWithLoc, addresses: *s } fn writeFunctionStarts(self: *MachO) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const seg = self.segments.items[self.header_segment_cmd_index.?]; // We need to sort by address first @@ -4133,7 +4150,7 @@ fn filterDataInCode( } pub fn writeDataInCode(self: *MachO) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var out_dice = std.ArrayList(macho.data_in_code_entry).init(gpa); defer out_dice.deinit(); @@ -4211,13 +4228,14 @@ fn addLocalToSymtab(self: *MachO, sym_loc: SymbolWithLoc, locals: *std.ArrayList if (sym.n_desc == N_BOUNDARY) return; // boundary symbol, skip if (sym.ext()) return; // an export lands in its own symtab section, skip if (self.symbolIsTemp(sym_loc)) return; // local temp symbol, skip + const gpa = self.base.comp.gpa; var out_sym = sym; - out_sym.n_strx = try self.strtab.insert(self.base.allocator, self.getSymbolName(sym_loc)); + out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(sym_loc)); try locals.append(out_sym); } fn writeSymtab(self: *MachO) !SymtabCtx { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var locals = std.ArrayList(macho.nlist_64).init(gpa); defer locals.deinit(); @@ -4322,7 +4340,7 @@ fn generateSymbolStabs( ) !void { log.debug("generating stabs for '{s}'", .{object.name}); - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; var debug_info = object.parseDwarfInfo(); var lookup = DwarfInfo.AbbrevLookupTable.init(gpa); @@ -4450,7 +4468,7 @@ fn generateSymbolStabsForSymbol( lookup: ?DwarfInfo.SubprogramLookupByName, buf: *[4]macho.nlist_64, ) ![]const macho.nlist_64 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const object = self.objects.items[sym_loc.getFile().?]; const sym = self.getSymbol(sym_loc); const sym_name = self.getSymbolName(sym_loc); @@ -4536,7 +4554,7 @@ fn generateSymbolStabsForSymbol( } pub fn writeStrtab(self: *MachO) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const seg = self.getLinkeditSegmentPtr(); const offset = seg.fileoff + seg.filesize; assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64))); @@ -4565,7 +4583,7 @@ const SymtabCtx = struct { }; pub fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const nstubs = @as(u32, @intCast(self.stub_table.lookup.count())); const ngot_entries = @as(u32, @intCast(self.got_table.lookup.count())); const nindirectsyms = nstubs * 2 + ngot_entries; @@ -4671,7 +4689,8 @@ pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *Cod const seg = self.segments.items[seg_id]; const offset = self.codesig_cmd.dataoff; - var buffer = std.ArrayList(u8).init(self.base.allocator); + const gpa = self.base.comp.gpa; + var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); try buffer.ensureTotalCapacityPrecise(code_sig.size()); try code_sig.writeAdhocSignature(comp, .{ @@ -4817,7 +4836,7 @@ pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void { } pub fn addUndefined(self: *MachO, name: []const u8, flags: RelocFlags) !u32 { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const gop = try self.getOrPutGlobalPtr(name); const global_index = self.getGlobalIndex(name).?; @@ -4842,7 +4861,8 @@ pub fn addUndefined(self: *MachO, name: []const u8, flags: RelocFlags) !u32 { } fn updateRelocActions(self: *MachO, global_index: u32, flags: RelocFlags) !void { - const act_gop = try self.actions.getOrPut(self.base.allocator, global_index); + const gpa = self.base.comp.gpa; + const act_gop = try self.actions.getOrPut(gpa, global_index); if (!act_gop.found_existing) { act_gop.value_ptr.* = .{}; } @@ -5022,7 +5042,7 @@ pub fn getOrPutGlobalPtr(self: *MachO, name: []const u8) !GetOrPutGlobalPtrResul if (self.getGlobalPtr(name)) |ptr| { return GetOrPutGlobalPtrResult{ .found_existing = true, .value_ptr = ptr }; } - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const global_index = try self.allocateGlobal(); const global_name = try gpa.dupe(u8, name); _ = try self.resolver.put(gpa, global_name, global_index); @@ -5171,6 +5191,7 @@ pub fn handleAndReportParseError( err: ParseError, ctx: *const ParseErrorCtx, ) error{OutOfMemory}!void { + const gpa = self.base.comp.gpa; const cpu_arch = self.base.options.target.cpu.arch; switch (err) { error.DylibAlreadyExists => {}, @@ -5188,7 +5209,7 @@ pub fn handleAndReportParseError( }, error.UnknownFileType => try self.reportParseError(path, "unknown file type", .{}), error.InvalidTarget, error.InvalidTargetFatLibrary => { - var targets_string = std.ArrayList(u8).init(self.base.allocator); + var targets_string = std.ArrayList(u8).init(gpa); defer targets_string.deinit(); if (ctx.detected_targets.items.len > 1) { @@ -5226,7 +5247,7 @@ fn reportMissingLibraryError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.misc_errors.ensureUnusedCapacity(gpa, 1); const notes = try gpa.alloc(File.ErrorMsg, checked_paths.len); errdefer gpa.free(notes); @@ -5246,7 +5267,7 @@ fn reportDependencyError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.misc_errors.ensureUnusedCapacity(gpa, 1); var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2); defer notes.deinit(); @@ -5266,7 +5287,7 @@ pub fn reportParseError( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.misc_errors.ensureUnusedCapacity(gpa, 1); var notes = try gpa.alloc(File.ErrorMsg, 1); errdefer gpa.free(notes); @@ -5283,7 +5304,7 @@ pub fn reportUnresolvedBoundarySymbol( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.misc_errors.ensureUnusedCapacity(gpa, 1); var notes = try gpa.alloc(File.ErrorMsg, 1); errdefer gpa.free(notes); @@ -5295,7 +5316,7 @@ pub fn reportUnresolvedBoundarySymbol( } pub fn reportUndefined(self: *MachO) error{OutOfMemory}!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; const count = self.unresolved.count(); try self.misc_errors.ensureUnusedCapacity(gpa, count); @@ -5327,7 +5348,7 @@ fn reportSymbolCollision( first: SymbolWithLoc, other: SymbolWithLoc, ) error{OutOfMemory}!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.misc_errors.ensureUnusedCapacity(gpa, 1); var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, 2); @@ -5355,7 +5376,7 @@ fn reportSymbolCollision( } fn reportUnhandledSymbolType(self: *MachO, sym_with_loc: SymbolWithLoc) error{OutOfMemory}!void { - const gpa = self.base.allocator; + const gpa = self.base.comp.gpa; try self.misc_errors.ensureUnusedCapacity(gpa, 1); const notes = try gpa.alloc(File.ErrorMsg, 1); diff --git a/src/main.zig b/src/main.zig index 681833857b9782cb68eae9590130814c6e767bc5..5685ddad27b24e052efacb9b5afcb5f5cfae2d64 100644 --- a/src/main.zig +++ b/src/main.zig @@ -269,8 +269,6 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi } } - defer log_scopes.deinit(gpa); - const cmd = args[1]; const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { @@ -321,7 +319,7 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi } else if (mem.eql(u8, cmd, "init")) { return cmdInit(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { - const host = try std.zig.system.resolveTargetQuery(.{}); + const host = resolveTargetQueryOrFatal(.{}); const stdout = io.getStdOut().writer(); return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, host); } else if (mem.eql(u8, cmd, "version")) { @@ -404,37 +402,69 @@ const usage_build_generic = \\ --global-cache-dir [path] Override the global cache directory \\ --zig-lib-dir [path] Override path to Zig installation lib directory \\ - \\Compile Options: + \\Global Compile Options: + \\ --name [name] Compilation unit name (not a file path) + \\ --libc [file] Provide a file which specifies libc paths + \\ -x language Treat subsequent input files as having type + \\ --dep [[import=]name] Add an entry to the next module's import table + \\ --mod [name] [src] Create a module based on the current per-module settings. + \\ The first module is the main module. + \\ "std" can be configured by leaving src blank. + \\ After a --mod argument, per-module settings are reset. + \\ --error-limit [num] Set the maximum amount of distinct error values + \\ -fllvm Force using LLVM as the codegen backend + \\ -fno-llvm Prevent using LLVM as the codegen backend + \\ -flibllvm Force using the LLVM API in the codegen backend + \\ -fno-libllvm Prevent using the LLVM API in the codegen backend + \\ -fclang Force using Clang as the C/C++ compilation backend + \\ -fno-clang Prevent using Clang as the C/C++ compilation backend + \\ -fPIE Force-enable Position Independent Executable + \\ -fno-PIE Force-disable Position Independent Executable + \\ -flto Force-enable Link Time Optimization (requires LLVM extensions) + \\ -fno-lto Force-disable Link Time Optimization + \\ -fdll-export-fns Mark exported functions as DLL exports (Windows) + \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports + \\ -freference-trace[=num] Show num lines of reference trace per compile error + \\ -fno-reference-trace Disable reference trace + \\ -fbuiltin Enable implicit builtin knowledge of functions + \\ -fno-builtin Disable implicit builtin knowledge of functions + \\ -ffunction-sections Places each function in a separate section + \\ -fno-function-sections All functions go into same section + \\ -fdata-sections Places each data in a separate section + \\ -fno-data-sections All data go into same section + \\ -fformatted-panics Enable formatted safety panics + \\ -fno-formatted-panics Disable formatted safety panics + \\ -fstructured-cfg (SPIR-V) force SPIR-V kernels to use structured control flow + \\ -fno-structured-cfg (SPIR-V) force SPIR-V kernels to not use structured control flow + \\ -mexec-model=[value] (WASI) Execution model + \\ + \\Per-Module Compile Options: \\ -target [name] -- see the targets command + \\ -O [mode] Choose what to optimize for + \\ Debug (default) Optimizations off, safety on + \\ ReleaseFast Optimize for performance, safety off + \\ ReleaseSafe Optimize for performance, safety on + \\ ReleaseSmall Optimize for small binary, safety off + \\ -ofmt=[fmt] Override target object format + \\ elf Executable and Linking Format + \\ c C source code + \\ wasm WebAssembly + \\ coff Common Object File Format (Windows) + \\ macho macOS relocatables + \\ spirv Standard, Portable Intermediate Representation V (SPIR-V) + \\ plan9 Plan 9 from Bell Labs object format + \\ hex (planned feature) Intel IHEX + \\ raw (planned feature) Dump machine code directly \\ -mcpu [cpu] Specify target CPU and feature set \\ -mcmodel=[default|tiny| Limit range of code and data virtual addresses \\ small|kernel| \\ medium|large] - \\ -x language Treat subsequent input files as having type \\ -mred-zone Force-enable the "red-zone" \\ -mno-red-zone Force-disable the "red-zone" \\ -fomit-frame-pointer Omit the stack frame pointer \\ -fno-omit-frame-pointer Store the stack frame pointer - \\ -mexec-model=[value] (WASI) Execution model - \\ --name [name] Override root name (not a file path) - \\ -O [mode] Choose what to optimize for - \\ Debug (default) Optimizations off, safety on - \\ ReleaseFast Optimize for performance, safety off - \\ ReleaseSafe Optimize for performance, safety on - \\ ReleaseSmall Optimize for small binary, safety off - \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name - \\ deps: [dep],[dep],... - \\ dep: [[import=]name] - \\ --deps [dep],[dep],... Set dependency names for the root package - \\ dep: [[import=]name] - \\ --main-mod-path Set the directory of the root module - \\ --error-limit [num] Set the maximum amount of distinct error values \\ -fPIC Force-enable Position Independent Code \\ -fno-PIC Force-disable Position Independent Code - \\ -fPIE Force-enable Position Independent Executable - \\ -fno-PIE Force-disable Position Independent Executable - \\ -flto Force-enable Link Time Optimization (requires LLVM extensions) - \\ -fno-lto Force-disable Link Time Optimization \\ -fstack-check Enable stack probing in unsafe builds \\ -fno-stack-check Disable stack probing in safe builds \\ -fstack-protector Enable stack protection in unsafe builds @@ -445,47 +475,18 @@ const usage_build_generic = \\ -fno-valgrind Omit valgrind client requests in debug builds \\ -fsanitize-thread Enable Thread Sanitizer \\ -fno-sanitize-thread Disable Thread Sanitizer - \\ -fdll-export-fns Mark exported functions as DLL exports (Windows) - \\ -fno-dll-export-fns Force-disable marking exported functions as DLL exports \\ -funwind-tables Always produce unwind table entries for all functions \\ -fno-unwind-tables Never produce unwind table entries - \\ -fllvm Force using LLVM as the codegen backend - \\ -fno-llvm Prevent using LLVM as the codegen backend - \\ -flibllvm Force using the LLVM API in the codegen backend - \\ -fno-libllvm Prevent using the LLVM API in the codegen backend - \\ -fclang Force using Clang as the C/C++ compilation backend - \\ -fno-clang Prevent using Clang as the C/C++ compilation backend - \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error - \\ -fno-reference-trace Disable reference trace \\ -ferror-tracing Enable error tracing in ReleaseFast mode \\ -fno-error-tracing Disable error tracing in Debug and ReleaseSafe mode \\ -fsingle-threaded Code assumes there is only one thread \\ -fno-single-threaded Code may not assume there is only one thread - \\ -fbuiltin Enable implicit builtin knowledge of functions - \\ -fno-builtin Disable implicit builtin knowledge of functions - \\ -ffunction-sections Places each function in a separate section - \\ -fno-function-sections All functions go into same section - \\ -fdata-sections Places each data in a separate section - \\ -fno-data-sections All data go into same section \\ -fstrip Omit debug symbols \\ -fno-strip Keep debug symbols - \\ -fformatted-panics Enable formatted safety panics - \\ -fno-formatted-panics Disable formatted safety panics - \\ -ofmt=[mode] Override target object format - \\ elf Executable and Linking Format - \\ c C source code - \\ wasm WebAssembly - \\ coff Common Object File Format (Windows) - \\ macho macOS relocatables - \\ spirv Standard, Portable Intermediate Representation V (SPIR-V) - \\ plan9 Plan 9 from Bell Labs object format - \\ hex (planned feature) Intel IHEX - \\ raw (planned feature) Dump machine code directly \\ -idirafter [dir] Add directory to AFTER include search path \\ -isystem [dir] Add directory to SYSTEM include search path \\ -I[dir] Add directory to include search path \\ -D[macro]=[value] Define C [macro] to [value] (1 if [value] omitted) - \\ --libc [file] Provide a file which specifies libc paths \\ -cflags [flags] -- Set extra flags for the next positional C source files \\ -rcflags [flags] -- Set extra flags for the next positional .rc source files \\ -rcincludes=[type] Set the type of includes to use when compiling .rc source files @@ -493,26 +494,8 @@ const usage_build_generic = \\ msvc Use msvc include paths (must be present on the system) \\ gnu Use mingw include paths (distributed with Zig) \\ none Do not use any autodetected include paths - \\ -fstructured-cfg (SPIR-V) force SPIR-V kernels to use structured control flow - \\ -fno-structured-cfg (SPIR-V) force SPIR-V kernels to not use structured control flow \\ - \\Link Options: - \\ -l[lib], --library [lib] Link against system library (only if actually used) - \\ -needed-l[lib], Link against system library (even if unused) - \\ --needed-library [lib] - \\ -weak-l[lib] link against system library marking it and all - \\ -weak_library [lib] referenced symbols as weak - \\ -L[d], --library-directory [d] Add a directory to the library search path - \\ -search_paths_first For each library search path, check for dynamic - \\ lib then static lib before proceeding to next path. - \\ -search_paths_first_static For each library search path, check for static - \\ lib then dynamic lib before proceeding to next path. - \\ -search_dylibs_first Search for dynamic libs in all library search - \\ paths, then static libs. - \\ -search_static_first Search for static libs in all library search - \\ paths, then dynamic libs. - \\ -search_dylibs_only Only search for dynamic libs. - \\ -search_static_only Only search for static libs. + \\Global Link Options: \\ -T[script], --script [script] Use a custom linker script \\ --version-script [path] Provide a version .map file \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) @@ -529,7 +512,6 @@ const usage_build_generic = \\ -fcompiler-rt Always include compiler-rt symbols in output \\ -fno-compiler-rt Prevent including compiler-rt symbols in output \\ -rdynamic Add all symbols to the dynamic symbol table - \\ -rpath [path] Add directory to the runtime library search path \\ -feach-lib-rpath Ensure adding rpath for each used dynamic library \\ -fno-each-lib-rpath Prevent adding rpath for each used dynamic library \\ -fallow-shlib-undefined Allows undefined symbols in shared libraries @@ -566,11 +548,6 @@ const usage_build_generic = \\ --subsystem [subsystem] (Windows) /SUBSYSTEM: to the linker \\ --stack [size] Override default stack size \\ --image-base [addr] Set base address for executable image - \\ -framework [name] (Darwin) link against framework - \\ -needed_framework [name] (Darwin) link against framework (even if unused) - \\ -needed_library [lib] link against system library (even if unused) - \\ -weak_framework [name] (Darwin) link against framework and mark it and all referenced symbols as weak - \\ -F[dir] (Darwin) add search path for frameworks \\ -install_name=[value] (Darwin) add dylib's install name \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature \\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation @@ -587,6 +564,30 @@ const usage_build_generic = \\ --max-memory=[bytes] (WebAssembly) maximum size of the linear memory \\ --shared-memory (WebAssembly) use shared linear memory \\ --global-base=[addr] (WebAssembly) where to start to place global data + \\ + \\Per-Module Link Options: + \\ -l[lib], --library [lib] Link against system library (only if actually used) + \\ -needed-l[lib], Link against system library (even if unused) + \\ --needed-library [lib] + \\ -weak-l[lib] link against system library marking it and all + \\ -weak_library [lib] referenced symbols as weak + \\ -L[d], --library-directory [d] Add a directory to the library search path + \\ -search_paths_first For each library search path, check for dynamic + \\ lib then static lib before proceeding to next path. + \\ -search_paths_first_static For each library search path, check for static + \\ lib then dynamic lib before proceeding to next path. + \\ -search_dylibs_first Search for dynamic libs in all library search + \\ paths, then static libs. + \\ -search_static_first Search for static libs in all library search + \\ paths, then dynamic libs. + \\ -search_dylibs_only Only search for dynamic libs. + \\ -search_static_only Only search for static libs. + \\ -rpath [path] Add directory to the runtime library search path + \\ -framework [name] (Darwin) link against framework + \\ -needed_framework [name] (Darwin) link against framework (even if unused) + \\ -needed_library [lib] link against system library (even if unused) + \\ -weak_framework [name] (Darwin) link against framework and mark it and all referenced symbols as weak + \\ -F[dir] (Darwin) add search path for frameworks \\ --export=[value] (WebAssembly) Force a symbol to be exported \\ \\Test Options: @@ -758,9 +759,24 @@ const Framework = struct { }; const CliModule = struct { - mod: *Package.Module, - /// still in CLI arg format - deps_str: []const u8, + paths: Package.Module.CreateOptions.Paths, + cc_argv: []const []const u8, + inherited: Package.Module.CreateOptions.Inherited, + target_arch_os_abi: ?[]const u8, + target_mcpu: ?[]const u8, + + deps: []const Dep, + resolved: ?*Package.Module, + + c_source_files_start: usize, + c_source_files_end: usize, + rc_source_files_start: usize, + rc_source_files_end: usize, + + pub const Dep = struct { + key: []const u8, + value: []const u8, + }; }; fn buildOutputType( @@ -769,17 +785,12 @@ fn buildOutputType( all_args: []const []const u8, arg_mode: ArgMode, ) !void { - var color: Color = .auto; - var optimize_mode: std.builtin.OptimizeMode = .Debug; var provided_name: ?[]const u8 = null; - var link_mode: ?std.builtin.LinkMode = null; var dll_export_fns: ?bool = null; - var single_threaded: ?bool = null; var root_src_file: ?[]const u8 = null; var version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }; var have_version = false; var compatibility_version: ?std.SemanticVersion = null; - var strip: ?bool = null; var formatted_panics: ?bool = null; var function_sections = false; var data_sections = false; @@ -807,30 +818,11 @@ fn buildOutputType( var emit_docs: Emit = .no; var emit_implib: Emit = .yes_default_path; var emit_implib_arg_provided = false; - var target_arch_os_abi: []const u8 = "native"; + var target_arch_os_abi: ?[]const u8 = null; var target_mcpu: ?[]const u8 = null; - var target_dynamic_linker: ?[]const u8 = null; - var target_ofmt: ?[]const u8 = null; - var output_mode: std.builtin.OutputMode = undefined; var emit_h: Emit = .no; var soname: SOName = undefined; - var ensure_libc_on_non_freestanding = false; - var ensure_libcpp_on_non_freestanding = false; - var link_libc = false; - var link_libcpp = false; - var link_libunwind = false; var want_native_include_dirs = false; - var want_pic: ?bool = null; - var want_pie: ?bool = null; - var want_lto: ?bool = null; - var want_unwind_tables: ?bool = null; - var want_sanitize_c: ?bool = null; - var want_stack_check: ?bool = null; - var want_stack_protector: ?u32 = null; - var want_red_zone: ?bool = null; - var omit_frame_pointer: ?bool = null; - var want_valgrind: ?bool = null; - var want_tsan: ?bool = null; var want_compiler_rt: ?bool = null; var rdynamic: bool = false; var linker_script: ?[]const u8 = null; @@ -841,15 +833,11 @@ fn buildOutputType( var linker_compress_debug_sections: ?link.CompressDebugSections = null; var linker_allow_shlib_undefined: ?bool = null; var linker_bind_global_refs_locally: ?bool = null; - var linker_import_memory: ?bool = null; - var linker_export_memory: ?bool = null; var linker_import_symbols: bool = false; var linker_import_table: bool = false; var linker_export_table: bool = false; - var linker_force_entry: ?bool = null; var linker_initial_memory: ?u64 = null; var linker_max_memory: ?u64 = null; - var linker_shared_memory: bool = false; var linker_global_base: ?u64 = null; var linker_print_gc_sections: bool = false; var linker_print_icf_sections: bool = false; @@ -869,23 +857,16 @@ fn buildOutputType( var linker_dynamicbase = true; var linker_optimization: ?u8 = null; var linker_module_definition_file: ?[]const u8 = null; - var test_evented_io = false; var test_no_exec = false; - var entry: ?[]const u8 = null; var force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{}; var stack_size_override: ?u64 = null; var image_base_override: ?u64 = null; - var use_llvm: ?bool = null; - var use_lib_llvm: ?bool = null; - var use_lld: ?bool = null; - var use_clang: ?bool = null; var link_eh_frame_hdr = false; var link_emit_relocs = false; var each_lib_rpath: ?bool = null; var build_id: ?std.zig.BuildId = null; var sysroot: ?[]const u8 = null; var libc_paths_file: ?[]const u8 = try EnvVar.ZIG_LIBC.get(arena); - var machine_code_model: std.builtin.CodeModel = .default; var runtime_args_start: ?usize = null; var test_filter: ?[]const u8 = null; var test_name_prefix: ?[]const u8 = null; @@ -893,12 +874,10 @@ fn buildOutputType( var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena); var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); - var main_mod_path: ?[]const u8 = null; var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.Target.SubSystem = null; var major_subsystem_version: ?u32 = null; var minor_subsystem_version: ?u32 = null; - var wasi_exec_model: ?std.builtin.WasiExecModel = null; var enable_link_snapshots: bool = false; var debug_incremental: bool = false; var install_name: ?[]const u8 = null; @@ -910,63 +889,100 @@ fn buildOutputType( var headerpad_size: ?u32 = null; var headerpad_max_install_names: bool = false; var dead_strip_dylibs: bool = false; + var contains_res_file: bool = false; var reference_trace: ?u32 = null; - var error_tracing: ?bool = null; var pdb_out_path: ?[]const u8 = null; var dwarf_format: ?std.dwarf.Format = null; var error_limit: ?Module.ErrorInt = null; var want_structured_cfg: ?bool = null; - // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names. - // This array is populated by zig cc frontend and then has to be converted to zig-style - // CPU features. - var llvm_m_args = std.ArrayList([]const u8).init(arena); - var system_libs = std.StringArrayHashMap(SystemLib).init(arena); - var wasi_emulated_libs = std.ArrayList(wasi_libc.CRTFile).init(arena); - var clang_argv = std.ArrayList([]const u8).init(arena); - var extra_cflags = std.ArrayList([]const u8).init(arena); - var extra_rcflags = std.ArrayList([]const u8).init(arena); // These are before resolving sysroot. - var lib_dir_args = std.ArrayList([]const u8).init(arena); - var rpath_list = std.ArrayList([]const u8).init(arena); + var lib_dir_args: std.ArrayListUnmanaged([]const u8) = .{}; + var extra_cflags: std.ArrayListUnmanaged([]const u8) = .{}; + var extra_rcflags: std.ArrayListUnmanaged([]const u8) = .{}; var symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .{}; - var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena); - var rc_source_files = std.ArrayList(Compilation.RcSourceFile).init(arena); + var rpath_list: std.ArrayListUnmanaged([]const u8) = .{}; var rc_includes: Compilation.RcIncludes = .any; - var res_files = std.ArrayList(Compilation.LinkObject).init(arena); var manifest_file: ?[]const u8 = null; - var link_objects = std.ArrayList(Compilation.LinkObject).init(arena); - var framework_dirs = std.ArrayList([]const u8).init(arena); + var link_objects: std.ArrayListUnmanaged(Compilation.LinkObject) = .{}; + var framework_dirs: std.ArrayListUnmanaged([]const u8) = .{}; var frameworks: std.StringArrayHashMapUnmanaged(Framework) = .{}; + var linker_export_symbol_names: std.ArrayListUnmanaged([]const u8) = .{}; + + // Tracks the position in c_source_files which have already their owner populated. + var c_source_files_owner_index: usize = 0; + // Tracks the position in rc_source_files which have already their owner populated. + var rc_source_files_owner_index: usize = 0; + // null means replace with the test executable binary var test_exec_args = std.ArrayList(?[]const u8).init(arena); - var linker_export_symbol_names = std.ArrayList([]const u8).init(arena); + + // These get set by CLI flags and then snapshotted when a `--mod` flag is + // encountered. + var mod_opts: Package.Module.CreateOptions.Inherited = .{}; + + // These get appended to by CLI flags and then slurped when a `--mod` flag + // is encountered. + var cssan: ClangSearchSanitizer = .{}; + var clang_argv: std.ArrayListUnmanaged([]const u8) = .{}; + var deps: std.ArrayListUnmanaged(CliModule.Dep) = .{}; + // Contains every module specified via --mod. The dependencies are added // after argument parsing is completed. We use a StringArrayHashMap to make - // error output consistent. - var modules = std.StringArrayHashMap(CliModule).init(arena); + // error output consistent. "root" is special. + var create_module: CreateModule = .{ + // Populated just before the call to `createModule`. + .global_cache_directory = undefined, + .object_format = null, + .dynamic_linker = null, + .modules = .{}, + .opts = .{ + .is_test = arg_mode == .zig_test, + // Populated while parsing CLI args. + .output_mode = undefined, + // Populated in the call to `createModule` for the root module. + .resolved_target = undefined, + .have_zcu = false, + // Populated just before the call to `createModule`. + .emit_llvm_ir = undefined, + // Populated just before the call to `createModule`. + .emit_llvm_bc = undefined, + // Populated just before the call to `createModule`. + .emit_bin = undefined, + // Populated just before the call to `createModule`. + .c_source_files_len = undefined, + }, + // Populated in the call to `createModule` for the root module. + .resolved_options = undefined, - // The dependency string for the root package - var root_deps_str: ?[]const u8 = null; + .system_libs = .{}, + .external_system_libs = .{}, + .resolved_system_libs = .{}, + .wasi_emulated_libs = .{}, + + .c_source_files = .{}, + .rc_source_files = .{}, + + .llvm_m_args = .{}, + }; // before arg parsing, check for the NO_COLOR environment variable // if it exists, default the color setting to .off // explicit --color arguments will still override this setting. // Disable color on WASI per https://github.com/WebAssembly/WASI/issues/162 - color = if (builtin.os.tag == .wasi or EnvVar.NO_COLOR.isSet()) .off else .auto; + var color: Color = if (builtin.os.tag == .wasi or EnvVar.NO_COLOR.isSet()) .off else .auto; switch (arg_mode) { .build, .translate_c, .zig_test, .run => { - var optimize_mode_string: ?[]const u8 = null; switch (arg_mode) { .build => |m| { - output_mode = m; + create_module.opts.output_mode = m; }, .translate_c => { emit_bin = .no; - output_mode = .Obj; + create_module.opts.output_mode = .Obj; }, .zig_test, .run => { - output_mode = .Exe; + create_module.opts.output_mode = .Exe; }, else => unreachable, } @@ -977,9 +993,6 @@ fn buildOutputType( .args = all_args[2..], }; - var cssan = ClangSearchSanitizer.init(gpa, &clang_argv); - defer cssan.map.deinit(); - var file_ext: ?Compilation.FileExt = null; args_loop: while (args_iter.next()) |arg| { if (mem.startsWith(u8, arg, "@")) { @@ -1002,49 +1015,73 @@ fn buildOutputType( } else { fatal("unexpected end-of-parameter mark: --", .{}); } + } else if (mem.eql(u8, arg, "--dep")) { + var it = mem.splitScalar(u8, args_iter.nextOrFatal(), '='); + const key = it.next().?; + const value = it.next() orelse key; + if (mem.eql(u8, key, "std") and !mem.eql(u8, value, "std")) { + fatal("unable to import as '{s}': conflicts with builtin module", .{ + key, + }); + } + for ([_][]const u8{ "root", "builtin" }) |name| { + if (mem.eql(u8, key, name)) { + fatal("unable to import as '{s}': conflicts with builtin module", .{ + key, + }); + } + } + try deps.append(arena, .{ + .key = key, + .value = value, + }); } else if (mem.eql(u8, arg, "--mod")) { - const info = args_iter.nextOrFatal(); - var info_it = mem.splitScalar(u8, info, ':'); - const mod_name = info_it.next() orelse fatal("expected non-empty argument after {s}", .{arg}); - const deps_str = info_it.next() orelse fatal("expected 'name:deps:path' after {s}", .{arg}); - const root_src_orig = info_it.rest(); - if (root_src_orig.len == 0) fatal("expected 'name:deps:path' after {s}", .{arg}); - if (mod_name.len == 0) fatal("empty name for module at '{s}'", .{root_src_orig}); + const mod_name = args_iter.nextOrFatal(); + const root_src_orig = args_iter.nextOrFatal(); + + const gop = try create_module.modules.getOrPut(arena, mod_name); + + if (gop.found_existing) { + fatal("unable to add module '{s}': already exists as '{s}'", .{ + mod_name, gop.value_ptr.paths.root_src_path, + }); + } + + // See duplicate logic: ModCreationGlobalFlags + create_module.opts.have_zcu = true; + if (mod_opts.single_threaded == false) + create_module.opts.any_non_single_threaded = true; + if (mod_opts.sanitize_thread == true) + create_module.opts.any_sanitize_thread = true; + if (mod_opts.unwind_tables == true) + create_module.opts.any_unwind_tables = true; const root_src = try introspect.resolvePath(arena, root_src_orig); - - for ([_][]const u8{ "std", "root", "builtin" }) |name| { - if (mem.eql(u8, mod_name, name)) { - fatal("unable to add module '{s}' -> '{s}': conflicts with builtin module", .{ - mod_name, root_src, - }); - } - } - - if (modules.get(mod_name)) |value| { - fatal("unable to add module '{s}' -> '{s}': already exists as '{s}'", .{ - mod_name, root_src, value.mod.root_src_path, - }); - } - - try modules.put(mod_name, .{ - .mod = try Package.Module.create(arena, .{ + try create_module.modules.put(arena, mod_name, .{ + .paths = .{ .root = .{ .root_dir = Cache.Directory.cwd(), .sub_path = fs.path.dirname(root_src) orelse "", }, .root_src_path = fs.path.basename(root_src), - .fully_qualified_name = mod_name, - }), - .deps_str = deps_str, + }, + .cc_argv = try clang_argv.toOwnedSlice(arena), + .inherited = mod_opts, + .target_arch_os_abi = target_arch_os_abi, + .target_mcpu = target_mcpu, + .deps = try deps.toOwnedSlice(arena), + .resolved = null, + .c_source_files_start = c_source_files_owner_index, + .c_source_files_end = create_module.c_source_files.items.len, + .rc_source_files_start = rc_source_files_owner_index, + .rc_source_files_end = create_module.rc_source_files.items.len, }); - } else if (mem.eql(u8, arg, "--deps")) { - if (root_deps_str != null) { - fatal("only one --deps argument is allowed", .{}); - } - root_deps_str = args_iter.nextOrFatal(); - } else if (mem.eql(u8, arg, "--main-mod-path")) { - main_mod_path = args_iter.nextOrFatal(); + cssan.reset(); + mod_opts = .{}; + target_arch_os_abi = null; + target_mcpu = null; + c_source_files_owner_index = create_module.c_source_files.items.len; + rc_source_files_owner_index = create_module.rc_source_files.items.len; } else if (mem.eql(u8, arg, "--error-limit")) { const next_arg = args_iter.nextOrFatal(); error_limit = std.fmt.parseUnsigned(Module.ErrorInt, next_arg, 0) catch |err| { @@ -1057,7 +1094,7 @@ fn buildOutputType( fatal("expected -- after -cflags", .{}); }; if (mem.eql(u8, next_arg, "--")) break; - try extra_cflags.append(next_arg); + try extra_cflags.append(arena, next_arg); } } else if (mem.eql(u8, arg, "-rcincludes")) { rc_includes = parseRcIncludes(args_iter.nextOrFatal()); @@ -1070,7 +1107,7 @@ fn buildOutputType( fatal("expected -- after -rcflags", .{}); }; if (mem.eql(u8, next_arg, "--")) break; - try extra_rcflags.append(next_arg); + try extra_rcflags.append(arena, next_arg); } } else if (mem.startsWith(u8, arg, "-fstructured-cfg")) { want_structured_cfg = true; @@ -1086,11 +1123,11 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--subsystem")) { subsystem = try parseSubSystem(args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-O")) { - optimize_mode_string = args_iter.nextOrFatal(); + mod_opts.optimize_mode = parseOptimizeMode(args_iter.nextOrFatal()); } else if (mem.startsWith(u8, arg, "-fentry=")) { - entry = arg["-fentry=".len..]; + create_module.opts.entry = .{ .named = arg["-fentry=".len..] }; } else if (mem.eql(u8, arg, "--force_undefined")) { - try force_undefined_symbols.put(gpa, args_iter.nextOrFatal(), {}); + try force_undefined_symbols.put(arena, args_iter.nextOrFatal(), {}); } else if (mem.eql(u8, arg, "--stack")) { const next_arg = args_iter.nextOrFatal(); stack_size_override = std.fmt.parseUnsigned(u64, next_arg, 0) catch |err| { @@ -1106,17 +1143,17 @@ fn buildOutputType( if (!mem.eql(u8, provided_name.?, fs.path.basename(provided_name.?))) fatal("invalid package name '{s}': cannot contain folder separators", .{provided_name.?}); } else if (mem.eql(u8, arg, "-rpath")) { - try rpath_list.append(args_iter.nextOrFatal()); + try rpath_list.append(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "--library-directory") or mem.eql(u8, arg, "-L")) { - try lib_dir_args.append(args_iter.nextOrFatal()); + try lib_dir_args.append(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-F")) { - try framework_dirs.append(args_iter.nextOrFatal()); + try framework_dirs.append(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-framework")) { - try frameworks.put(gpa, args_iter.nextOrFatal(), .{}); + try frameworks.put(arena, args_iter.nextOrFatal(), .{}); } else if (mem.eql(u8, arg, "-weak_framework")) { - try frameworks.put(gpa, args_iter.nextOrFatal(), .{ .weak = true }); + try frameworks.put(arena, args_iter.nextOrFatal(), .{ .weak = true }); } else if (mem.eql(u8, arg, "-needed_framework")) { - try frameworks.put(gpa, args_iter.nextOrFatal(), .{ .needed = true }); + try frameworks.put(arena, args_iter.nextOrFatal(), .{ .needed = true }); } else if (mem.eql(u8, arg, "-install_name")) { install_name = args_iter.nextOrFatal(); } else if (mem.startsWith(u8, arg, "--compress-debug-sections=")) { @@ -1168,7 +1205,7 @@ fn buildOutputType( // We don't know whether this library is part of libc // or libc++ until we resolve the target, so we append // to the list for now. - try system_libs.put(args_iter.nextOrFatal(), .{ + try create_module.system_libs.put(arena, args_iter.nextOrFatal(), .{ .needed = false, .weak = false, .preferred_mode = lib_preferred_mode, @@ -1179,38 +1216,37 @@ fn buildOutputType( mem.eql(u8, arg, "-needed_library")) { const next_arg = args_iter.nextOrFatal(); - try system_libs.put(next_arg, .{ + try create_module.system_libs.put(arena, next_arg, .{ .needed = true, .weak = false, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }); } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) { - try system_libs.put(args_iter.nextOrFatal(), .{ + try create_module.system_libs.put(arena, args_iter.nextOrFatal(), .{ .needed = false, .weak = true, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }); } else if (mem.eql(u8, arg, "-D")) { - try clang_argv.append(arg); - try clang_argv.append(args_iter.nextOrFatal()); + try clang_argv.appendSlice(arena, &.{ arg, args_iter.nextOrFatal() }); } else if (mem.eql(u8, arg, "-I")) { - try cssan.addIncludePath(.I, arg, args_iter.nextOrFatal(), false); + try cssan.addIncludePath(arena, &clang_argv, .I, arg, args_iter.nextOrFatal(), false); } else if (mem.eql(u8, arg, "-isystem")) { - try cssan.addIncludePath(.isystem, arg, args_iter.nextOrFatal(), false); + try cssan.addIncludePath(arena, &clang_argv, .isystem, arg, args_iter.nextOrFatal(), false); } else if (mem.eql(u8, arg, "-iwithsysroot")) { - try cssan.addIncludePath(.iwithsysroot, arg, args_iter.nextOrFatal(), false); + try cssan.addIncludePath(arena, &clang_argv, .iwithsysroot, arg, args_iter.nextOrFatal(), false); } else if (mem.eql(u8, arg, "-idirafter")) { - try cssan.addIncludePath(.idirafter, arg, args_iter.nextOrFatal(), false); + try cssan.addIncludePath(arena, &clang_argv, .idirafter, arg, args_iter.nextOrFatal(), false); } else if (mem.eql(u8, arg, "-iframework")) { const path = args_iter.nextOrFatal(); - try cssan.addIncludePath(.iframework, arg, path, false); - try framework_dirs.append(path); // Forward to the backend as -F + try cssan.addIncludePath(arena, &clang_argv, .iframework, arg, path, false); + try framework_dirs.append(arena, path); // Forward to the backend as -F } else if (mem.eql(u8, arg, "-iframeworkwithsysroot")) { const path = args_iter.nextOrFatal(); - try cssan.addIncludePath(.iframeworkwithsysroot, arg, path, false); - try framework_dirs.append(path); // Forward to the backend as -F + try cssan.addIncludePath(arena, &clang_argv, .iframeworkwithsysroot, arg, path, false); + try framework_dirs.append(arena, path); // Forward to the backend as -F } else if (mem.eql(u8, arg, "--version")) { const next_arg = args_iter.nextOrFatal(); version = std.SemanticVersion.parse(next_arg) catch |err| { @@ -1222,21 +1258,21 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-mcpu")) { target_mcpu = args_iter.nextOrFatal(); } else if (mem.eql(u8, arg, "-mcmodel")) { - machine_code_model = parseCodeModel(args_iter.nextOrFatal()); + mod_opts.code_model = parseCodeModel(args_iter.nextOrFatal()); + } else if (mem.startsWith(u8, arg, "-mcmodel=")) { + mod_opts.code_model = parseCodeModel(arg["-mcmodel=".len..]); } else if (mem.startsWith(u8, arg, "-ofmt=")) { - target_ofmt = arg["-ofmt=".len..]; + create_module.object_format = arg["-ofmt=".len..]; } else if (mem.startsWith(u8, arg, "-mcpu=")) { target_mcpu = arg["-mcpu=".len..]; - } else if (mem.startsWith(u8, arg, "-mcmodel=")) { - machine_code_model = parseCodeModel(arg["-mcmodel=".len..]); } else if (mem.startsWith(u8, arg, "-O")) { - optimize_mode_string = arg["-O".len..]; + mod_opts.optimize_mode = parseOptimizeMode(arg["-O".len..]); } else if (mem.eql(u8, arg, "--dynamic-linker")) { - target_dynamic_linker = args_iter.nextOrFatal(); + create_module.dynamic_linker = args_iter.nextOrFatal(); } else if (mem.eql(u8, arg, "--sysroot")) { - sysroot = args_iter.nextOrFatal(); - try clang_argv.append("-isysroot"); - try clang_argv.append(sysroot.?); + const next_arg = args_iter.nextOrFatal(); + sysroot = next_arg; + try clang_argv.appendSlice(arena, &.{ "-isysroot", next_arg }); } else if (mem.eql(u8, arg, "--libc")) { libc_paths_file = args_iter.nextOrFatal(); } else if (mem.eql(u8, arg, "--test-filter")) { @@ -1258,7 +1294,7 @@ fn buildOutputType( warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{}); _ = args_iter.nextOrFatal(); } else { - try log_scopes.append(gpa, args_iter.nextOrFatal()); + try log_scopes.append(arena, args_iter.nextOrFatal()); } } else if (mem.eql(u8, arg, "--listen")) { const next_arg = args_iter.nextOrFatal(); @@ -1298,7 +1334,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--test-cmd-bin")) { try test_exec_args.append(null); } else if (mem.eql(u8, arg, "--test-evented-io")) { - test_evented_io = true; + create_module.opts.test_evented_io = true; } else if (mem.eql(u8, arg, "--test-no-exec")) { test_no_exec = true; } else if (mem.eql(u8, arg, "-ftime-report")) { @@ -1306,65 +1342,65 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-fstack-report")) { stack_report = true; } else if (mem.eql(u8, arg, "-fPIC")) { - want_pic = true; + mod_opts.pic = true; } else if (mem.eql(u8, arg, "-fno-PIC")) { - want_pic = false; + mod_opts.pic = false; } else if (mem.eql(u8, arg, "-fPIE")) { - want_pie = true; + create_module.opts.pie = true; } else if (mem.eql(u8, arg, "-fno-PIE")) { - want_pie = false; + create_module.opts.pie = false; } else if (mem.eql(u8, arg, "-flto")) { - want_lto = true; + create_module.opts.lto = true; } else if (mem.eql(u8, arg, "-fno-lto")) { - want_lto = false; + create_module.opts.lto = false; } else if (mem.eql(u8, arg, "-funwind-tables")) { - want_unwind_tables = true; + mod_opts.unwind_tables = true; } else if (mem.eql(u8, arg, "-fno-unwind-tables")) { - want_unwind_tables = false; + mod_opts.unwind_tables = false; } else if (mem.eql(u8, arg, "-fstack-check")) { - want_stack_check = true; + mod_opts.stack_check = true; } else if (mem.eql(u8, arg, "-fno-stack-check")) { - want_stack_check = false; + mod_opts.stack_check = false; } else if (mem.eql(u8, arg, "-fstack-protector")) { - want_stack_protector = Compilation.default_stack_protector_buffer_size; + mod_opts.stack_protector = Compilation.default_stack_protector_buffer_size; } else if (mem.eql(u8, arg, "-fno-stack-protector")) { - want_stack_protector = 0; + mod_opts.stack_protector = 0; } else if (mem.eql(u8, arg, "-mred-zone")) { - want_red_zone = true; + mod_opts.red_zone = true; } else if (mem.eql(u8, arg, "-mno-red-zone")) { - want_red_zone = false; + mod_opts.red_zone = false; } else if (mem.eql(u8, arg, "-fomit-frame-pointer")) { - omit_frame_pointer = true; + mod_opts.omit_frame_pointer = true; } else if (mem.eql(u8, arg, "-fno-omit-frame-pointer")) { - omit_frame_pointer = false; + mod_opts.omit_frame_pointer = false; } else if (mem.eql(u8, arg, "-fsanitize-c")) { - want_sanitize_c = true; + mod_opts.sanitize_c = true; } else if (mem.eql(u8, arg, "-fno-sanitize-c")) { - want_sanitize_c = false; + mod_opts.sanitize_c = false; } else if (mem.eql(u8, arg, "-fvalgrind")) { - want_valgrind = true; + mod_opts.valgrind = true; } else if (mem.eql(u8, arg, "-fno-valgrind")) { - want_valgrind = false; + mod_opts.valgrind = false; } else if (mem.eql(u8, arg, "-fsanitize-thread")) { - want_tsan = true; + mod_opts.sanitize_thread = true; } else if (mem.eql(u8, arg, "-fno-sanitize-thread")) { - want_tsan = false; + mod_opts.sanitize_thread = false; } else if (mem.eql(u8, arg, "-fllvm")) { - use_llvm = true; + create_module.opts.use_llvm = true; } else if (mem.eql(u8, arg, "-fno-llvm")) { - use_llvm = false; + create_module.opts.use_llvm = false; } else if (mem.eql(u8, arg, "-flibllvm")) { - use_lib_llvm = true; + create_module.opts.use_lib_llvm = true; } else if (mem.eql(u8, arg, "-fno-libllvm")) { - use_lib_llvm = false; + create_module.opts.use_lib_llvm = false; } else if (mem.eql(u8, arg, "-flld")) { - use_lld = true; + create_module.opts.use_lld = true; } else if (mem.eql(u8, arg, "-fno-lld")) { - use_lld = false; + create_module.opts.use_lld = false; } else if (mem.eql(u8, arg, "-fclang")) { - use_clang = true; + create_module.opts.use_clang = true; } else if (mem.eql(u8, arg, "-fno-clang")) { - use_clang = false; + create_module.opts.use_clang = false; } else if (mem.eql(u8, arg, "-freference-trace")) { reference_trace = 256; } else if (mem.startsWith(u8, arg, "-freference-trace=")) { @@ -1375,9 +1411,9 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-fno-reference-trace")) { reference_trace = null; } else if (mem.eql(u8, arg, "-ferror-tracing")) { - error_tracing = true; + mod_opts.error_tracing = true; } else if (mem.eql(u8, arg, "-fno-error-tracing")) { - error_tracing = false; + mod_opts.error_tracing = false; } else if (mem.eql(u8, arg, "-rdynamic")) { rdynamic = true; } else if (mem.eql(u8, arg, "-fsoname")) { @@ -1432,11 +1468,11 @@ fn buildOutputType( emit_implib = .no; emit_implib_arg_provided = true; } else if (mem.eql(u8, arg, "-dynamic")) { - link_mode = .Dynamic; + create_module.opts.link_mode = .Dynamic; lib_preferred_mode = .Dynamic; lib_search_strategy = .mode_first; } else if (mem.eql(u8, arg, "-static")) { - link_mode = .Static; + create_module.opts.link_mode = .Static; lib_preferred_mode = .Static; lib_search_strategy = .no_fallback; } else if (mem.eql(u8, arg, "-fdll-export-fns")) { @@ -1447,9 +1483,9 @@ fn buildOutputType( show_builtin = true; emit_bin = .no; } else if (mem.eql(u8, arg, "-fstrip")) { - strip = true; + mod_opts.strip = true; } else if (mem.eql(u8, arg, "-fno-strip")) { - strip = false; + mod_opts.strip = false; } else if (mem.eql(u8, arg, "-gdwarf32")) { dwarf_format = .@"32"; } else if (mem.eql(u8, arg, "-gdwarf64")) { @@ -1459,9 +1495,9 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-fno-formatted-panics")) { formatted_panics = false; } else if (mem.eql(u8, arg, "-fsingle-threaded")) { - single_threaded = true; + mod_opts.single_threaded = true; } else if (mem.eql(u8, arg, "-fno-single-threaded")) { - single_threaded = false; + mod_opts.single_threaded = false; } else if (mem.eql(u8, arg, "-ffunction-sections")) { function_sections = true; } else if (mem.eql(u8, arg, "-fno-function-sections")) { @@ -1518,13 +1554,16 @@ fn buildOutputType( fatal("unsupported linker extension flag: -z {s}", .{z_arg}); } } else if (mem.eql(u8, arg, "--import-memory")) { - linker_import_memory = true; + create_module.opts.import_memory = true; } else if (mem.eql(u8, arg, "-fentry")) { - linker_force_entry = true; + switch (create_module.opts.entry) { + .default, .disabled => create_module.opts.entry = .enabled, + .enabled, .named => {}, + } } else if (mem.eql(u8, arg, "-fno-entry")) { - linker_force_entry = false; + create_module.opts.entry = .disabled; } else if (mem.eql(u8, arg, "--export-memory")) { - linker_export_memory = true; + create_module.opts.export_memory = true; } else if (mem.eql(u8, arg, "--import-symbols")) { linker_import_symbols = true; } else if (mem.eql(u8, arg, "--import-table")) { @@ -1536,11 +1575,11 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "--max-memory=")) { linker_max_memory = parseIntSuffix(arg, "--max-memory=".len); } else if (mem.eql(u8, arg, "--shared-memory")) { - linker_shared_memory = true; + create_module.opts.shared_memory = true; } else if (mem.startsWith(u8, arg, "--global-base=")) { linker_global_base = parseIntSuffix(arg, "--global-base=".len); } else if (mem.startsWith(u8, arg, "--export=")) { - try linker_export_symbol_names.append(arg["--export=".len..]); + try linker_export_symbol_names.append(arena, arg["--export=".len..]); } else if (mem.eql(u8, arg, "-Bsymbolic")) { linker_bind_global_refs_locally = true; } else if (mem.eql(u8, arg, "--gc-sections")) { @@ -1585,37 +1624,37 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "-T")) { linker_script = arg[2..]; } else if (mem.startsWith(u8, arg, "-L")) { - try lib_dir_args.append(arg[2..]); + try lib_dir_args.append(arena, arg[2..]); } else if (mem.startsWith(u8, arg, "-F")) { - try framework_dirs.append(arg[2..]); + try framework_dirs.append(arena, arg[2..]); } else if (mem.startsWith(u8, arg, "-l")) { // We don't know whether this library is part of libc // or libc++ until we resolve the target, so we append // to the list for now. - try system_libs.put(arg["-l".len..], .{ + try create_module.system_libs.put(arena, arg["-l".len..], .{ .needed = false, .weak = false, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }); } else if (mem.startsWith(u8, arg, "-needed-l")) { - try system_libs.put(arg["-needed-l".len..], .{ + try create_module.system_libs.put(arena, arg["-needed-l".len..], .{ .needed = true, .weak = false, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }); } else if (mem.startsWith(u8, arg, "-weak-l")) { - try system_libs.put(arg["-weak-l".len..], .{ + try create_module.system_libs.put(arena, arg["-weak-l".len..], .{ .needed = false, .weak = true, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }); } else if (mem.startsWith(u8, arg, "-D")) { - try clang_argv.append(arg); + try clang_argv.append(arena, arg); } else if (mem.startsWith(u8, arg, "-I")) { - try cssan.addIncludePath(.I, arg, arg[2..], true); + try cssan.addIncludePath(arena, &clang_argv, .I, arg, arg[2..], true); } else if (mem.eql(u8, arg, "-x")) { const lang = args_iter.nextOrFatal(); if (mem.eql(u8, lang, "none")) { @@ -1626,23 +1665,27 @@ fn buildOutputType( fatal("language not recognized: '{s}'", .{lang}); } } else if (mem.startsWith(u8, arg, "-mexec-model=")) { - wasi_exec_model = std.meta.stringToEnum(std.builtin.WasiExecModel, arg["-mexec-model=".len..]) orelse { - fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{arg["-mexec-model=".len..]}); - }; + create_module.opts.wasi_exec_model = parseWasiExecModel(arg["-mexec-model=".len..]); } else { fatal("unrecognized parameter: '{s}'", .{arg}); } - } else switch (file_ext orelse - Compilation.classifyFileExt(arg)) { - .object, .static_library, .shared_library => try link_objects.append(.{ .path = arg }), - .res => try res_files.append(.{ .path = arg }), + } else switch (file_ext orelse Compilation.classifyFileExt(arg)) { + .object, .static_library, .shared_library => { + try link_objects.append(arena, .{ .path = arg }); + }, + .res => { + try link_objects.append(arena, .{ .path = arg }); + contains_res_file = true; + }, .manifest => { if (manifest_file) |other| { fatal("only one manifest file can be specified, found '{s}' after '{s}'", .{ arg, other }); } else manifest_file = arg; }, .assembly, .assembly_with_cpp, .c, .cpp, .h, .ll, .bc, .m, .mm, .cu => { - try c_source_files.append(.{ + try create_module.c_source_files.append(arena, .{ + // Populated after module creation. + .owner = undefined, .src_path = arg, .extra_flags = try arena.dupe([]const u8, extra_cflags.items), // duped when parsing the args. @@ -1650,7 +1693,9 @@ fn buildOutputType( }); }, .rc => { - try rc_source_files.append(.{ + try create_module.rc_source_files.append(arena, .{ + // Populated after module creation. + .owner = undefined, .src_path = arg, .extra_flags = try arena.dupe([]const u8, extra_rcflags.items), }); @@ -1668,18 +1713,14 @@ fn buildOutputType( }, } } - if (optimize_mode_string) |s| { - optimize_mode = std.meta.stringToEnum(std.builtin.OptimizeMode, s) orelse - fatal("unrecognized optimization mode: '{s}'", .{s}); - } }, .cc, .cpp => { if (build_options.only_c) unreachable; emit_h = .no; soname = .no; - ensure_libc_on_non_freestanding = true; - ensure_libcpp_on_non_freestanding = arg_mode == .cpp; + create_module.opts.ensure_libc_on_non_freestanding = true; + create_module.opts.ensure_libcpp_on_non_freestanding = arg_mode == .cpp; want_native_include_dirs = true; // Clang's driver enables this switch unconditionally. // Disabling the emission of .eh_frame_hdr can unexpectedly break @@ -1733,24 +1774,30 @@ fn buildOutputType( } }, .other => { - try clang_argv.appendSlice(it.other_args); + try clang_argv.appendSlice(arena, it.other_args); }, - .positional => switch (file_ext orelse - Compilation.classifyFileExt(mem.sliceTo(it.only_arg, 0))) { + .positional => switch (file_ext orelse Compilation.classifyFileExt(mem.sliceTo(it.only_arg, 0))) { .assembly, .assembly_with_cpp, .c, .cpp, .ll, .bc, .h, .m, .mm, .cu => { - try c_source_files.append(.{ + try create_module.c_source_files.append(arena, .{ + // Populated after module creation. + .owner = undefined, .src_path = it.only_arg, .ext = file_ext, // duped while parsing the args. }); }, - .unknown, .shared_library, .object, .static_library => try link_objects.append(.{ - .path = it.only_arg, - .must_link = must_link, - }), - .res => try res_files.append(.{ - .path = it.only_arg, - .must_link = must_link, - }), + .unknown, .shared_library, .object, .static_library => { + try link_objects.append(arena, .{ + .path = it.only_arg, + .must_link = must_link, + }); + }, + .res => { + try link_objects.append(arena, .{ + .path = it.only_arg, + .must_link = must_link, + }); + contains_res_file = true; + }, .manifest => { if (manifest_file) |other| { fatal("only one manifest file can be specified, found '{s}' after previously specified manifest '{s}'", .{ it.only_arg, other }); @@ -1760,7 +1807,11 @@ fn buildOutputType( linker_module_definition_file = it.only_arg; }, .rc => { - try rc_source_files.append(.{ .src_path = it.only_arg }); + try create_module.rc_source_files.append(arena, .{ + // Populated after module creation. + .owner = undefined, + .src_path = it.only_arg, + }); }, .zig => { if (root_src_file) |other| { @@ -1777,13 +1828,13 @@ fn buildOutputType( // more control over what's in the resulting // binary: no extra rpaths and DSO filename exactly // as provided. Hello, Go. - try link_objects.append(.{ + try link_objects.append(arena, .{ .path = it.only_arg, .must_link = must_link, .loption = true, }); } else { - try system_libs.put(it.only_arg, .{ + try create_module.system_libs.put(arena, it.only_arg, .{ .needed = needed, .weak = false, .preferred_mode = lib_preferred_mode, @@ -1796,16 +1847,16 @@ fn buildOutputType( // Never mind what we're doing, just pass the args directly. For example --help. return process.exit(try clangMain(arena, all_args)); }, - .pic => want_pic = true, - .no_pic => want_pic = false, - .pie => want_pie = true, - .no_pie => want_pie = false, - .lto => want_lto = true, - .no_lto => want_lto = false, - .red_zone => want_red_zone = true, - .no_red_zone => want_red_zone = false, - .omit_frame_pointer => omit_frame_pointer = true, - .no_omit_frame_pointer => omit_frame_pointer = false, + .pic => mod_opts.pic = true, + .no_pic => mod_opts.pic = false, + .pie => create_module.opts.pie = true, + .no_pie => create_module.opts.pie = false, + .lto => create_module.opts.lto = true, + .no_lto => create_module.opts.lto = false, + .red_zone => mod_opts.red_zone = true, + .no_red_zone => mod_opts.red_zone = false, + .omit_frame_pointer => mod_opts.omit_frame_pointer = true, + .no_omit_frame_pointer => mod_opts.omit_frame_pointer = false, .function_sections => function_sections = true, .no_function_sections => function_sections = false, .data_sections => data_sections = true, @@ -1814,23 +1865,23 @@ fn buildOutputType( .no_builtin => no_builtin = true, .color_diagnostics => color = .on, .no_color_diagnostics => color = .off, - .stack_check => want_stack_check = true, - .no_stack_check => want_stack_check = false, + .stack_check => mod_opts.stack_check = true, + .no_stack_check => mod_opts.stack_check = false, .stack_protector => { - if (want_stack_protector == null) { - want_stack_protector = Compilation.default_stack_protector_buffer_size; + if (mod_opts.stack_protector == null) { + mod_opts.stack_protector = Compilation.default_stack_protector_buffer_size; } }, - .no_stack_protector => want_stack_protector = 0, - .unwind_tables => want_unwind_tables = true, - .no_unwind_tables => want_unwind_tables = false, + .no_stack_protector => mod_opts.stack_protector = 0, + .unwind_tables => mod_opts.unwind_tables = true, + .no_unwind_tables => mod_opts.unwind_tables = false, .nostdlib => { - ensure_libc_on_non_freestanding = false; - ensure_libcpp_on_non_freestanding = false; + create_module.opts.ensure_libc_on_non_freestanding = false; + create_module.opts.ensure_libcpp_on_non_freestanding = false; }, - .nostdlib_cpp => ensure_libcpp_on_non_freestanding = false, + .nostdlib_cpp => create_module.opts.ensure_libcpp_on_non_freestanding = false, .shared => { - link_mode = .Dynamic; + create_module.opts.link_mode = .Dynamic; is_shared_lib = true; }, .rdynamic => rdynamic = true, @@ -1870,7 +1921,7 @@ fn buildOutputType( } else if (mem.eql(u8, linker_arg, "--no-as-needed")) { needed = true; } else if (mem.eql(u8, linker_arg, "-no-pie")) { - want_pie = false; + create_module.opts.pie = false; } else if (mem.eql(u8, linker_arg, "--sort-common")) { // from ld.lld(1): --sort-common is ignored for GNU compatibility, // this ignores plain --sort-common @@ -1912,50 +1963,50 @@ fn buildOutputType( if (mem.eql(u8, level, "s") or mem.eql(u8, level, "z")) { - optimize_mode = .ReleaseSmall; + mod_opts.optimize_mode = .ReleaseSmall; } else if (mem.eql(u8, level, "1") or mem.eql(u8, level, "2") or mem.eql(u8, level, "3") or mem.eql(u8, level, "4") or mem.eql(u8, level, "fast")) { - optimize_mode = .ReleaseFast; + mod_opts.optimize_mode = .ReleaseFast; } else if (mem.eql(u8, level, "g") or mem.eql(u8, level, "0")) { - optimize_mode = .Debug; + mod_opts.optimize_mode = .Debug; } else { - try clang_argv.appendSlice(it.other_args); + try clang_argv.appendSlice(arena, it.other_args); } }, .debug => { - strip = false; + mod_opts.strip = false; if (mem.eql(u8, it.only_arg, "g")) { // We handled with strip = false above. } else if (mem.eql(u8, it.only_arg, "g1") or mem.eql(u8, it.only_arg, "gline-tables-only")) { // We handled with strip = false above. but we also want reduced debug info. - try clang_argv.append("-gline-tables-only"); + try clang_argv.append(arena, "-gline-tables-only"); } else { - try clang_argv.appendSlice(it.other_args); + try clang_argv.appendSlice(arena, it.other_args); } }, .gdwarf32 => { - strip = false; + mod_opts.strip = false; dwarf_format = .@"32"; }, .gdwarf64 => { - strip = false; + mod_opts.strip = false; dwarf_format = .@"64"; }, .sanitize => { if (mem.eql(u8, it.only_arg, "undefined")) { - want_sanitize_c = true; + mod_opts.sanitize_c = true; } else if (mem.eql(u8, it.only_arg, "thread")) { - want_tsan = true; + mod_opts.sanitize_thread = true; } else { - try clang_argv.appendSlice(it.other_args); + try clang_argv.appendSlice(arena, it.other_args); } }, .linker_script => linker_script = it.only_arg, @@ -1964,59 +2015,57 @@ fn buildOutputType( // Have Clang print more infos, some tools such as CMake // parse this to discover any implicit include and // library dir to look-up into. - try clang_argv.append("-v"); + try clang_argv.append(arena, "-v"); }, .dry_run => { // This flag means "dry run". Clang will not actually output anything // to the file system. verbose_link = true; disable_c_depfile = true; - try clang_argv.append("-###"); + try clang_argv.append(arena, "-###"); }, .for_linker => try linker_args.append(it.only_arg), .linker_input_z => { try linker_args.append("-z"); try linker_args.append(it.only_arg); }, - .lib_dir => try lib_dir_args.append(it.only_arg), + .lib_dir => try lib_dir_args.append(arena, it.only_arg), .mcpu => target_mcpu = it.only_arg, - .m => try llvm_m_args.append(it.only_arg), + .m => try create_module.llvm_m_args.append(arena, it.only_arg), .dep_file => { disable_c_depfile = true; - try clang_argv.appendSlice(it.other_args); + try clang_argv.appendSlice(arena, it.other_args); }, .dep_file_to_stdout => { // -M, -MM // "Like -MD, but also implies -E and writes to stdout by default" // "Like -MMD, but also implies -E and writes to stdout by default" c_out_mode = .preprocessor; disable_c_depfile = true; - try clang_argv.appendSlice(it.other_args); + try clang_argv.appendSlice(arena, it.other_args); }, - .framework_dir => try framework_dirs.append(it.only_arg), - .framework => try frameworks.put(gpa, it.only_arg, .{}), + .framework_dir => try framework_dirs.append(arena, it.only_arg), + .framework => try frameworks.put(arena, it.only_arg, .{}), .nostdlibinc => want_native_include_dirs = false, - .strip => strip = true, + .strip => mod_opts.strip = true, .exec_model => { - wasi_exec_model = std.meta.stringToEnum(std.builtin.WasiExecModel, it.only_arg) orelse { - fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{it.only_arg}); - }; + create_module.opts.wasi_exec_model = parseWasiExecModel(it.only_arg); }, .sysroot => { sysroot = it.only_arg; }, .entry => { - entry = it.only_arg; + create_module.opts.entry = .{ .named = it.only_arg }; }, .force_undefined_symbol => { - try force_undefined_symbols.put(gpa, it.only_arg, {}); + try force_undefined_symbols.put(arena, it.only_arg, {}); }, - .weak_library => try system_libs.put(it.only_arg, .{ + .weak_library => try create_module.system_libs.put(arena, it.only_arg, .{ .needed = false, .weak = true, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }), - .weak_framework => try frameworks.put(gpa, it.only_arg, .{ .weak = true }), + .weak_framework => try frameworks.put(arena, it.only_arg, .{ .weak = true }), .headerpad_max_install_names => headerpad_max_install_names = true, .compress_debug_sections => { if (it.only_arg.len == 0) { @@ -2077,14 +2126,14 @@ fn buildOutputType( } provided_name = name[prefix..end]; } else if (mem.eql(u8, arg, "-rpath")) { - try rpath_list.append(linker_args_it.nextOrFatal()); + try rpath_list.append(arena, linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "--subsystem")) { subsystem = try parseSubSystem(linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "-I") or mem.eql(u8, arg, "--dynamic-linker") or mem.eql(u8, arg, "-dynamic-linker")) { - target_dynamic_linker = linker_args_it.nextOrFatal(); + create_module.dynamic_linker = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "-E") or mem.eql(u8, arg, "--export-dynamic") or mem.eql(u8, arg, "-export-dynamic")) @@ -2145,9 +2194,9 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-Bsymbolic")) { linker_bind_global_refs_locally = true; } else if (mem.eql(u8, arg, "--import-memory")) { - linker_import_memory = true; + create_module.opts.import_memory = true; } else if (mem.eql(u8, arg, "--export-memory")) { - linker_export_memory = true; + create_module.opts.export_memory = true; } else if (mem.eql(u8, arg, "--import-symbols")) { linker_import_symbols = true; } else if (mem.eql(u8, arg, "--import-table")) { @@ -2155,7 +2204,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--export-table")) { linker_export_table = true; } else if (mem.eql(u8, arg, "--no-entry")) { - linker_force_entry = false; + create_module.opts.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| { @@ -2167,14 +2216,14 @@ fn buildOutputType( fatal("unable to parse max memory size '{s}': {s}", .{ next_arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--shared-memory")) { - linker_shared_memory = true; + create_module.opts.shared_memory = true; } else if (mem.eql(u8, arg, "--global-base")) { const next_arg = linker_args_it.nextOrFatal(); linker_global_base = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| { fatal("unable to parse global base '{s}': {s}", .{ next_arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--export")) { - try linker_export_symbol_names.append(linker_args_it.nextOrFatal()); + try linker_export_symbol_names.append(arena, linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "--compress-debug-sections")) { const arg1 = linker_args_it.nextOrFatal(); linker_compress_debug_sections = std.meta.stringToEnum(link.CompressDebugSections, arg1) orelse { @@ -2232,9 +2281,9 @@ fn buildOutputType( }; have_version = true; } else if (mem.eql(u8, arg, "-e") or mem.eql(u8, arg, "--entry")) { - entry = linker_args_it.nextOrFatal(); + create_module.opts.entry = .{ .named = linker_args_it.nextOrFatal() }; } else if (mem.eql(u8, arg, "-u")) { - try force_undefined_symbols.put(gpa, linker_args_it.nextOrFatal(), {}); + try force_undefined_symbols.put(arena, linker_args_it.nextOrFatal(), {}); } else if (mem.eql(u8, arg, "--stack") or mem.eql(u8, arg, "-stack_size")) { const stack_size = linker_args_it.nextOrFatal(); stack_size_override = std.fmt.parseUnsigned(u64, stack_size, 0) catch |err| { @@ -2276,7 +2325,7 @@ fn buildOutputType( { // -s, --strip-all Strip all symbols // -S, --strip-debug Strip debugging symbols - strip = true; + mod_opts.strip = true; } else if (mem.eql(u8, arg, "--start-group") or mem.eql(u8, arg, "--end-group")) { @@ -2307,27 +2356,27 @@ fn buildOutputType( fatal("unable to parse minor subsystem version '{s}': {s}", .{ minor, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-framework")) { - try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{}); + try frameworks.put(arena, linker_args_it.nextOrFatal(), .{}); } else if (mem.eql(u8, arg, "-weak_framework")) { - try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{ .weak = true }); + try frameworks.put(arena, linker_args_it.nextOrFatal(), .{ .weak = true }); } else if (mem.eql(u8, arg, "-needed_framework")) { - try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{ .needed = true }); + try frameworks.put(arena, linker_args_it.nextOrFatal(), .{ .needed = true }); } else if (mem.eql(u8, arg, "-needed_library")) { - try system_libs.put(linker_args_it.nextOrFatal(), .{ + try create_module.system_libs.put(arena, linker_args_it.nextOrFatal(), .{ .weak = false, .needed = true, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }); } else if (mem.startsWith(u8, arg, "-weak-l")) { - try system_libs.put(arg["-weak-l".len..], .{ + try create_module.system_libs.put(arena, arg["-weak-l".len..], .{ .weak = true, .needed = false, .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, }); } else if (mem.eql(u8, arg, "-weak_library")) { - try system_libs.put(linker_args_it.nextOrFatal(), .{ + try create_module.system_libs.put(arena, linker_args_it.nextOrFatal(), .{ .weak = true, .needed = false, .preferred_mode = lib_preferred_mode, @@ -2361,7 +2410,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-install_name")) { install_name = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "-force_load")) { - try link_objects.append(.{ + try link_objects.append(arena, .{ .path = linker_args_it.nextOrFatal(), .must_link = true, }); @@ -2402,22 +2451,22 @@ fn buildOutputType( } } - if (want_sanitize_c) |wsc| { - if (wsc and optimize_mode == .ReleaseFast) { - optimize_mode = .ReleaseSafe; + if (mod_opts.sanitize_c) |wsc| { + if (wsc and mod_opts.optimize_mode == .ReleaseFast) { + mod_opts.optimize_mode = .ReleaseSafe; } } switch (c_out_mode) { .link => { - output_mode = if (is_shared_lib) .Lib else .Exe; + create_module.opts.output_mode = if (is_shared_lib) .Lib else .Exe; emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out; if (emit_llvm) { fatal("-emit-llvm cannot be used when linking", .{}); } }, .object => { - output_mode = .Obj; + create_module.opts.output_mode = .Obj; if (emit_llvm) { emit_bin = .no; if (out_path) |p| { @@ -2434,7 +2483,7 @@ fn buildOutputType( } }, .assembly => { - output_mode = .Obj; + create_module.opts.output_mode = .Obj; emit_bin = .no; if (emit_llvm) { if (out_path) |p| { @@ -2451,9 +2500,9 @@ fn buildOutputType( } }, .preprocessor => { - output_mode = .Obj; + create_module.opts.output_mode = .Obj; // An error message is generated when there is more than 1 C source file. - if (c_source_files.items.len != 1) { + if (create_module.c_source_files.items.len != 1) { // For example `zig cc` and no args should print the "no input files" message. return process.exit(try clangMain(arena, all_args)); } @@ -2465,7 +2514,7 @@ fn buildOutputType( } }, } - if (c_source_files.items.len == 0 and + if (create_module.c_source_files.items.len == 0 and link_objects.items.len == 0 and root_src_file == null) { @@ -2476,57 +2525,230 @@ fn buildOutputType( }, } - { - // Resolve module dependencies - var it = modules.iterator(); - while (it.next()) |kv| { - const deps_str = kv.value_ptr.deps_str; - var deps_it = ModuleDepIterator.init(deps_str); - while (deps_it.next()) |dep| { - if (dep.expose.len == 0) { - fatal("module '{s}' depends on '{s}' with a blank name", .{ - kv.key_ptr.*, dep.name, - }); - } - - for ([_][]const u8{ "std", "root", "builtin" }) |name| { - if (mem.eql(u8, dep.expose, name)) { - fatal("unable to add module '{s}' under name '{s}': conflicts with builtin module", .{ - dep.name, dep.expose, - }); - } - } - - const dep_mod = modules.get(dep.name) orelse { - fatal("module '{s}' depends on module '{s}' which does not exist", .{ - kv.key_ptr.*, dep.name, - }); - }; - - try kv.value_ptr.mod.deps.put(arena, dep.expose, dep_mod.mod); - } - } - } - - if (arg_mode == .build and optimize_mode == .ReleaseSmall and strip == null) - strip = true; - - if (arg_mode == .translate_c and c_source_files.items.len != 1) { - fatal("translate-c expects exactly 1 source file (found {d})", .{c_source_files.items.len}); + if (arg_mode == .translate_c and create_module.c_source_files.items.len != 1) { + fatal("translate-c expects exactly 1 source file (found {d})", .{create_module.c_source_files.items.len}); } if (root_src_file == null and arg_mode == .zig_test) { fatal("`zig test` expects a zig source file argument", .{}); } + if (root_src_file) |unresolved_src_path| { + if (create_module.modules.count() != 0) { + fatal("main module provided both by '--mod {s} {}{s}' and by positional argument '{s}'", .{ + create_module.modules.keys()[0], + create_module.modules.values()[0].paths.root, + create_module.modules.values()[0].paths.root_src_path, + unresolved_src_path, + }); + } + + // See duplicate logic: ModCreationGlobalFlags + create_module.opts.have_zcu = true; + if (mod_opts.single_threaded == false) + create_module.opts.any_non_single_threaded = true; + if (mod_opts.sanitize_thread == true) + create_module.opts.any_sanitize_thread = true; + if (mod_opts.unwind_tables == true) + create_module.opts.any_unwind_tables = true; + + const src_path = try introspect.resolvePath(arena, unresolved_src_path); + try create_module.modules.put(arena, "main", .{ + .paths = .{ + .root = .{ + .root_dir = Cache.Directory.cwd(), + .sub_path = fs.path.dirname(src_path) orelse "", + }, + .root_src_path = fs.path.basename(src_path), + }, + .cc_argv = try clang_argv.toOwnedSlice(arena), + .inherited = mod_opts, + .target_arch_os_abi = target_arch_os_abi, + .target_mcpu = target_mcpu, + .deps = try deps.toOwnedSlice(arena), + .resolved = null, + .c_source_files_start = c_source_files_owner_index, + .c_source_files_end = create_module.c_source_files.items.len, + .rc_source_files_start = rc_source_files_owner_index, + .rc_source_files_end = create_module.rc_source_files.items.len, + }); + cssan.reset(); + mod_opts = .{}; + target_arch_os_abi = null; + target_mcpu = null; + c_source_files_owner_index = create_module.c_source_files.items.len; + rc_source_files_owner_index = create_module.rc_source_files.items.len; + } + + if (c_source_files_owner_index != create_module.c_source_files.items.len) { + fatal("C source file '{s}' has no parent module", .{ + create_module.c_source_files.items[c_source_files_owner_index].src_path, + }); + } + + if (rc_source_files_owner_index != create_module.rc_source_files.items.len) { + fatal("resource file '{s}' has no parent module", .{ + create_module.rc_source_files.items[rc_source_files_owner_index].src_path, + }); + } + + const self_exe_path: ?[]const u8 = if (!process.can_spawn) + null + else + introspect.findZigExePath(arena) catch |err| { + fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); + }; + + var zig_lib_directory: Compilation.Directory = d: { + if (override_lib_dir) |unresolved_lib_dir| { + const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir); + break :d .{ + .path = lib_dir, + .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { + fatal("unable to open zig lib directory '{s}': {s}", .{ lib_dir, @errorName(err) }); + }, + }; + } else if (builtin.os.tag == .wasi) { + break :d getWasiPreopen("/lib"); + } else if (self_exe_path) |p| { + break :d introspect.findZigLibDirFromSelfExe(arena, p) catch |err| { + fatal("unable to find zig installation directory: {s}", .{@errorName(err)}); + }; + } else { + unreachable; + } + }; + defer zig_lib_directory.handle.close(); + + var global_cache_directory: Compilation.Directory = l: { + if (override_global_cache_dir) |p| { + break :l .{ + .handle = try fs.cwd().makeOpenPath(p, .{}), + .path = p, + }; + } + if (builtin.os.tag == .wasi) { + break :l getWasiPreopen("/cache"); + } + const p = try introspect.resolveGlobalCacheDir(arena); + break :l .{ + .handle = try fs.cwd().makeOpenPath(p, .{}), + .path = p, + }; + }; + defer global_cache_directory.handle.close(); + + create_module.global_cache_directory = global_cache_directory; + create_module.opts.emit_llvm_ir = emit_llvm_ir != .no; + create_module.opts.emit_llvm_bc = emit_llvm_bc != .no; + create_module.opts.emit_bin = emit_bin != .no; + create_module.opts.c_source_files_len = create_module.c_source_files.items.len; + + const main_mod = try createModule(gpa, arena, &create_module, 0, null, zig_lib_directory); + for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| { + if (cli_mod.resolved == null) + fatal("module '{s}' declared but not used", .{key}); + } + + // When you're testing std, the main module is std. In that case, + // we'll just set the std module to the main one, since avoiding + // the errors caused by duplicating it is more effort than it's + // worth. + const main_mod_is_std = m: { + const std_path = try fs.path.resolve(arena, &.{ + zig_lib_directory.path orelse ".", "std", "std.zig", + }); + const main_path = try fs.path.resolve(arena, &.{ + main_mod.root.root_dir.path orelse ".", + main_mod.root.sub_path, + main_mod.root_src_path, + }); + break :m mem.eql(u8, main_path, std_path); + }; + + const std_mod = m: { + if (main_mod_is_std) break :m main_mod; + if (create_module.modules.get("std")) |cli_mod| break :m cli_mod.resolved.?; + + break :m try Package.Module.create(arena, .{ + .global_cache_directory = global_cache_directory, + .paths = .{ + .root = .{ + .root_dir = zig_lib_directory, + .sub_path = "std", + }, + .root_src_path = "std.zig", + }, + .fully_qualified_name = "std", + .cc_argv = &.{}, + .inherited = .{}, + .global = create_module.resolved_options, + .parent = main_mod, + .builtin_mod = main_mod.getBuiltinDependency(), + }); + }; + + const root_mod = if (arg_mode == .zig_test) root_mod: { + const test_mod = if (test_runner_path) |test_runner| test_mod: { + const test_mod = try Package.Module.create(arena, .{ + .global_cache_directory = global_cache_directory, + .paths = .{ + .root = .{ + .root_dir = Cache.Directory.cwd(), + .sub_path = fs.path.dirname(test_runner) orelse "", + }, + .root_src_path = fs.path.basename(test_runner), + }, + .fully_qualified_name = "root", + .cc_argv = &.{}, + .inherited = .{}, + .global = create_module.resolved_options, + .parent = main_mod, + .builtin_mod = main_mod.getBuiltinDependency(), + }); + test_mod.deps = try main_mod.deps.clone(arena); + break :test_mod test_mod; + } else try Package.Module.create(arena, .{ + .global_cache_directory = global_cache_directory, + .paths = .{ + .root = .{ + .root_dir = zig_lib_directory, + }, + .root_src_path = "test_runner.zig", + }, + .fully_qualified_name = "root", + .cc_argv = &.{}, + .inherited = .{}, + .global = create_module.resolved_options, + .parent = main_mod, + .builtin_mod = main_mod.getBuiltinDependency(), + }); + + break :root_mod test_mod; + } else main_mod; + + const target = main_mod.resolved_target.result; + + if (target.ofmt != .coff) { + if (manifest_file != null) { + fatal("manifest file is not allowed unless the target object format is coff (Windows/UEFI)", .{}); + } + if (create_module.rc_source_files.items.len != 0) { + fatal("rc files are not allowed unless the target object format is coff (Windows/UEFI)", .{}); + } + if (contains_res_file) { + fatal("res files are not allowed unless the target object format is coff (Windows/UEFI)", .{}); + } + } + const root_name = if (provided_name) |n| n else blk: { if (arg_mode == .zig_test) { break :blk "test"; } else if (root_src_file) |file| { const basename = fs.path.basename(file); break :blk basename[0 .. basename.len - fs.path.extension(basename).len]; - } else if (c_source_files.items.len >= 1) { - const basename = fs.path.basename(c_source_files.items[0].src_path); + } else if (create_module.c_source_files.items.len >= 1) { + const basename = fs.path.basename(create_module.c_source_files.items[0].src_path); break :blk basename[0 .. basename.len - fs.path.extension(basename).len]; } else if (link_objects.items.len >= 1) { const basename = fs.path.basename(link_objects.items[0].path); @@ -2534,11 +2756,8 @@ fn buildOutputType( } else if (emit_bin == .yes) { const basename = fs.path.basename(emit_bin.yes); break :blk basename[0 .. basename.len - fs.path.extension(basename).len]; - } else if (rc_source_files.items.len >= 1) { - const basename = fs.path.basename(rc_source_files.items[0].src_path); - break :blk basename[0 .. basename.len - fs.path.extension(basename).len]; - } else if (res_files.items.len >= 1) { - const basename = fs.path.basename(res_files.items[0].path); + } else if (create_module.rc_source_files.items.len >= 1) { + const basename = fs.path.basename(create_module.rc_source_files.items[0].src_path); break :blk basename[0 .. basename.len - fs.path.extension(basename).len]; } else if (show_builtin) { break :blk "builtin"; @@ -2552,292 +2771,34 @@ fn buildOutputType( } }; - var target_parse_options: std.Target.Query.ParseOptions = .{ - .arch_os_abi = target_arch_os_abi, - .cpu_features = target_mcpu, - .dynamic_linker = target_dynamic_linker, - .object_format = target_ofmt, - }; - - // Before passing the mcpu string in for parsing, we convert any -m flags that were - // passed in via zig cc to zig-style. - if (llvm_m_args.items.len != 0) { - // If this returns null, we let it fall through to the case below which will - // run the full parse function and do proper error handling. - if (std.Target.Query.parseCpuArch(target_parse_options)) |cpu_arch| { - var llvm_to_zig_name = std.StringHashMap([]const u8).init(gpa); - defer llvm_to_zig_name.deinit(); - - for (cpu_arch.allFeaturesList()) |feature| { - const llvm_name = feature.llvm_name orelse continue; - try llvm_to_zig_name.put(llvm_name, feature.name); - } - - var mcpu_buffer = std.ArrayList(u8).init(gpa); - defer mcpu_buffer.deinit(); - - try mcpu_buffer.appendSlice(target_mcpu orelse "baseline"); - - for (llvm_m_args.items) |llvm_m_arg| { - if (mem.startsWith(u8, llvm_m_arg, "mno-")) { - const llvm_name = llvm_m_arg["mno-".len..]; - const zig_name = llvm_to_zig_name.get(llvm_name) orelse { - fatal("target architecture {s} has no LLVM CPU feature named '{s}'", .{ - @tagName(cpu_arch), llvm_name, - }); - }; - try mcpu_buffer.append('-'); - try mcpu_buffer.appendSlice(zig_name); - } else if (mem.startsWith(u8, llvm_m_arg, "m")) { - const llvm_name = llvm_m_arg["m".len..]; - const zig_name = llvm_to_zig_name.get(llvm_name) orelse { - fatal("target architecture {s} has no LLVM CPU feature named '{s}'", .{ - @tagName(cpu_arch), llvm_name, - }); - }; - try mcpu_buffer.append('+'); - try mcpu_buffer.appendSlice(zig_name); - } else { - unreachable; - } - } - - const adjusted_target_mcpu = try arena.dupe(u8, mcpu_buffer.items); - std.log.debug("adjusted target_mcpu: {s}", .{adjusted_target_mcpu}); - target_parse_options.cpu_features = adjusted_target_mcpu; - } - } - - const target_query = try parseTargetQueryOrReportFatalError(arena, target_parse_options); - const target = try std.zig.system.resolveTargetQuery(target_query); - - if (target.os.tag != .freestanding) { - if (ensure_libc_on_non_freestanding) - link_libc = true; - if (ensure_libcpp_on_non_freestanding) - link_libcpp = true; - } - - if (linker_force_entry) |force| { - if (!force) { - entry = null; - } else if (entry == null and output_mode == .Exe) { - entry = switch (target.ofmt) { - .coff => "wWinMainCRTStartup", - .macho => "_main", - .elf, .plan9 => "_start", - .wasm => defaultWasmEntryName(wasi_exec_model), - else => |tag| fatal("No default entry point available for output format {s}", .{@tagName(tag)}), - }; - } - } else if (entry == null and target.isWasm() and output_mode == .Exe) { - // For WebAssembly the compiler defaults to setting the entry name when no flags are set. - entry = defaultWasmEntryName(wasi_exec_model); - } - - if (target.ofmt == .coff) { - // Now that we know the target supports resources, - // we can add the res files as link objects. - for (res_files.items) |res_file| { - try link_objects.append(res_file); - } - } else { - if (manifest_file != null) { - fatal("manifest file is not allowed unless the target object format is coff (Windows/UEFI)", .{}); - } - if (rc_source_files.items.len != 0) { - fatal("rc files are not allowed unless the target object format is coff (Windows/UEFI)", .{}); - } - if (res_files.items.len != 0) { - fatal("res files are not allowed unless the target object format is coff (Windows/UEFI)", .{}); - } - } - - if (target.cpu.arch.isWasm()) blk: { - if (single_threaded == null) { - single_threaded = true; - } - if (link_mode) |mode| { - if (mode == .Dynamic) { - if (linker_export_memory != null and linker_export_memory.?) { - fatal("flags '-dynamic' and '--export-memory' are incompatible", .{}); - } - // User did not supply `--export-memory` which is incompatible with -dynamic, therefore - // set the flag to false to ensure it does not get enabled by default. - linker_export_memory = false; - } - } - if (wasi_exec_model != null and wasi_exec_model.? == .reactor) { - if (entry) |entry_name| { - if (!mem.eql(u8, "_initialize", entry_name)) { - fatal("the entry symbol of the reactor model must be '_initialize', but found '{s}'", .{entry_name}); - } - } - } - if (linker_shared_memory) { - if (output_mode == .Obj) { - fatal("shared memory is not allowed in object files", .{}); - } - - if (!target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.atomics)) or - !target.cpu.features.isEnabled(@intFromEnum(std.Target.wasm.Feature.bulk_memory))) - { - fatal("'atomics' and 'bulk-memory' features must be enabled to use shared memory", .{}); - } - break :blk; - } - - // Single-threaded is the default for WebAssembly, so only when the user specified `-fno_single-threaded` - // can they enable multithreaded WebAssembly builds. - const is_single_threaded = single_threaded.?; - if (!is_single_threaded) { - fatal("'-fno-single-threaded' requires the linker feature shared-memory to be enabled using '--shared-memory'", .{}); - } - } - - if (use_lld) |opt| { - if (opt and target.isDarwin()) { - fatal("LLD requested with Mach-O object format. Only the self-hosted linker is supported for this target.", .{}); - } - } - - if (want_lto) |opt| { - if (opt and target.isDarwin()) { - fatal("LTO is not yet supported with the Mach-O object format. More details: https://github.com/ziglang/zig/issues/8680", .{}); - } - } - - if (comptime builtin.target.isDarwin()) { - // If we want to link against frameworks, we need system headers. - if (framework_dirs.items.len > 0 or frameworks.count() > 0) - want_native_include_dirs = true; - } - // Resolve the library path arguments with respect to sysroot. - var lib_dirs = std.ArrayList([]const u8).init(arena); + var lib_dirs: std.ArrayListUnmanaged([]const u8) = .{}; if (sysroot) |root| { + try lib_dirs.ensureUnusedCapacity(arena, lib_dir_args.items.len * 2); for (lib_dir_args.items) |dir| { if (fs.path.isAbsolute(dir)) { const stripped_dir = dir[fs.path.diskDesignator(dir).len..]; const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir }); - try lib_dirs.append(full_path); + lib_dirs.appendAssumeCapacity(full_path); } - try lib_dirs.append(dir); + lib_dirs.appendAssumeCapacity(dir); } } else { lib_dirs = lib_dir_args; } lib_dir_args = undefined; // From here we use lib_dirs instead. - const self_exe_path: ?[]const u8 = if (!process.can_spawn) - null - else - introspect.findZigExePath(arena) catch |err| { - fatal("unable to find zig self exe path: {s}", .{@errorName(err)}); - }; - - var zig_lib_directory: Compilation.Directory = d: { - if (override_lib_dir) |unresolved_lib_dir| { - const lib_dir = try introspect.resolvePath(arena, unresolved_lib_dir); - break :d .{ - .path = lib_dir, - .handle = fs.cwd().openDir(lib_dir, .{}) catch |err| { - fatal("unable to open zig lib directory '{s}': {s}", .{ lib_dir, @errorName(err) }); - }, - }; - } else if (builtin.os.tag == .wasi) { - break :d getWasiPreopen("/lib"); - } else if (self_exe_path) |p| { - break :d introspect.findZigLibDirFromSelfExe(arena, p) catch |err| { - fatal("unable to find zig installation directory: {s}", .{@errorName(err)}); - }; - } else { - unreachable; - } - }; - defer zig_lib_directory.handle.close(); - - // First, remove libc, libc++, and compiler_rt libraries from the system libraries list. - // We need to know whether the set of system libraries contains anything besides these - // to decide whether to trigger native path detection logic. - var external_system_libs: std.MultiArrayList(struct { - name: []const u8, - info: SystemLib, - }) = .{}; - - var resolved_system_libs: std.MultiArrayList(struct { - name: []const u8, - lib: Compilation.SystemLib, - }) = .{}; - - var libc_installation: ?LibCInstallation = null; - if (libc_paths_file) |paths_file| { - libc_installation = LibCInstallation.parse(arena, paths_file, target) catch |err| { - fatal("unable to parse libc paths file at path {s}: {s}", .{ paths_file, @errorName(err) }); - }; + if (main_mod.resolved_target.is_native_os and target.isDarwin()) { + // If we want to link against frameworks, we need system headers. + if (framework_dirs.items.len > 0 or frameworks.count() > 0) + want_native_include_dirs = true; } - for (system_libs.keys(), system_libs.values()) |lib_name, info| { - if (target.is_libc_lib_name(lib_name)) { - link_libc = true; - continue; - } - if (target.is_libcpp_lib_name(lib_name)) { - link_libcpp = true; - continue; - } - switch (target_util.classifyCompilerRtLibName(target, lib_name)) { - .none => {}, - .only_libunwind, .both => { - link_libunwind = true; - continue; - }, - .only_compiler_rt => { - warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name}); - continue; - }, - } - - if (target.isMinGW()) { - const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| { - fatal("failed to check zig installation for DLL import libs: {s}", .{ - @errorName(err), - }); - }; - if (exists) { - try resolved_system_libs.append(arena, .{ - .name = lib_name, - .lib = .{ - .needed = true, - .weak = false, - .path = null, - }, - }); - continue; - } - } - - if (fs.path.isAbsolute(lib_name)) { - fatal("cannot use absolute path as a system library: {s}", .{lib_name}); - } - - if (target.os.tag == .wasi) { - if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| { - try wasi_emulated_libs.append(crt_file); - continue; - } - } - - try external_system_libs.append(arena, .{ - .name = lib_name, - .info = info, - }); - } - // After this point, external_system_libs is used instead of system_libs. - // Trigger native system library path detection if necessary. - if (sysroot == null and target_query.isNativeOs() and target_query.isNativeAbi() and - (external_system_libs.len != 0 or want_native_include_dirs)) + if (sysroot == null and + main_mod.resolved_target.is_native_os and + main_mod.resolved_target.is_native_abi and + (create_module.external_system_libs.len != 0 or want_native_include_dirs)) { const paths = std.zig.system.NativePaths.detect(arena, target) catch |err| { fatal("unable to detect native system paths: {s}", .{@errorName(err)}); @@ -2846,20 +2807,27 @@ fn buildOutputType( warn("{s}", .{warning}); } - try clang_argv.ensureUnusedCapacity(paths.include_dirs.items.len * 2); + try clang_argv.ensureUnusedCapacity(arena, paths.include_dirs.items.len * 2); for (paths.include_dirs.items) |include_dir| { clang_argv.appendAssumeCapacity("-isystem"); clang_argv.appendAssumeCapacity(include_dir); } - try framework_dirs.appendSlice(paths.framework_dirs.items); - try lib_dirs.appendSlice(paths.lib_dirs.items); - try rpath_list.appendSlice(paths.rpaths.items); + try framework_dirs.appendSlice(arena, paths.framework_dirs.items); + try lib_dirs.appendSlice(arena, paths.lib_dirs.items); + try rpath_list.appendSlice(arena, paths.rpaths.items); + } + + var libc_installation: ?LibCInstallation = null; + if (libc_paths_file) |paths_file| { + libc_installation = LibCInstallation.parse(arena, paths_file, target) catch |err| { + fatal("unable to parse libc paths file at path {s}: {s}", .{ paths_file, @errorName(err) }); + }; } if (builtin.target.os.tag == .windows and target.abi == .msvc and - external_system_libs.len != 0) + create_module.external_system_libs.len != 0) { if (libc_installation == null) { libc_installation = try LibCInstallation.findNative(.{ @@ -2868,7 +2836,10 @@ fn buildOutputType( .target = target, }); - try lib_dirs.appendSlice(&.{ libc_installation.?.msvc_lib_dir.?, libc_installation.?.kernel32_lib_dir.? }); + try lib_dirs.appendSlice(arena, &.{ + libc_installation.?.msvc_lib_dir.?, + libc_installation.?.kernel32_lib_dir.?, + }); } } @@ -2888,7 +2859,7 @@ fn buildOutputType( preferred_mode: std.builtin.LinkMode, }).init(arena); - syslib: for (external_system_libs.items(.name), external_system_libs.items(.info)) |lib_name, info| { + syslib: for (create_module.external_system_libs.items(.name), create_module.external_system_libs.items(.info)) |lib_name, info| { // Checked in the first pass above while looking for libc libraries. assert(!fs.path.isAbsolute(lib_name)); @@ -2908,8 +2879,8 @@ fn buildOutputType( )) { const path = try arena.dupe(u8, test_path.items); switch (info.preferred_mode) { - .Static => try link_objects.append(.{ .path = path }), - .Dynamic => try resolved_system_libs.append(arena, .{ + .Static => try link_objects.append(arena, .{ .path = path }), + .Dynamic => try create_module.resolved_system_libs.append(arena, .{ .name = lib_name, .lib = .{ .needed = info.needed, @@ -2942,8 +2913,8 @@ fn buildOutputType( )) { const path = try arena.dupe(u8, test_path.items); switch (info.fallbackMode()) { - .Static => try link_objects.append(.{ .path = path }), - .Dynamic => try resolved_system_libs.append(arena, .{ + .Static => try link_objects.append(arena, .{ .path = path }), + .Dynamic => try create_module.resolved_system_libs.append(arena, .{ .name = lib_name, .lib = .{ .needed = info.needed, @@ -2976,8 +2947,8 @@ fn buildOutputType( )) { const path = try arena.dupe(u8, test_path.items); switch (info.preferred_mode) { - .Static => try link_objects.append(.{ .path = path }), - .Dynamic => try resolved_system_libs.append(arena, .{ + .Static => try link_objects.append(arena, .{ .path = path }), + .Dynamic => try create_module.resolved_system_libs.append(arena, .{ .name = lib_name, .lib = .{ .needed = info.needed, @@ -3000,8 +2971,8 @@ fn buildOutputType( )) { const path = try arena.dupe(u8, test_path.items); switch (info.fallbackMode()) { - .Static => try link_objects.append(.{ .path = path }), - .Dynamic => try resolved_system_libs.append(arena, .{ + .Static => try link_objects.append(arena, .{ .path = path }), + .Dynamic => try create_module.resolved_system_libs.append(arena, .{ .name = lib_name, .lib = .{ .needed = info.needed, @@ -3035,7 +3006,8 @@ fn buildOutputType( process.exit(1); } } - // After this point, resolved_system_libs is used instead of external_system_libs. + // After this point, create_module.resolved_system_libs is used instead of + // create_module.external_system_libs. // We now repeat part of the process for frameworks. var resolved_frameworks = std.ArrayList(Compilation.Framework).init(arena); @@ -3090,10 +3062,10 @@ fn buildOutputType( } // After this point, resolved_frameworks is used instead of frameworks. - if (output_mode == .Obj and (target.ofmt == .coff or target.ofmt == .macho)) { - const total_obj_count = c_source_files.items.len + + if (create_module.opts.output_mode == .Obj and (target.ofmt == .coff or target.ofmt == .macho)) { + const total_obj_count = create_module.c_source_files.items.len + @intFromBool(root_src_file != null) + - rc_source_files.items.len + + create_module.rc_source_files.items.len + link_objects.items.len; if (total_obj_count > 1) { fatal("{s} does not support linking multiple objects into one", .{@tagName(target.ofmt)}); @@ -3141,8 +3113,8 @@ fn buildOutputType( .basename = try std.zig.binNameAlloc(arena, .{ .root_name = root_name, .target = target, - .output_mode = output_mode, - .link_mode = link_mode, + .output_mode = create_module.opts.output_mode, + .link_mode = create_module.opts.link_mode, .version = optional_version, }), }, @@ -3260,9 +3232,9 @@ fn buildOutputType( }; defer emit_docs_resolved.deinit(); - const is_exe_or_dyn_lib = switch (output_mode) { + const is_exe_or_dyn_lib = switch (create_module.opts.output_mode) { .Obj => false, - .Lib => (link_mode orelse .Static) == .Dynamic, + .Lib => (create_module.opts.link_mode orelse .Static) == .Dynamic, .Exe => true, }; // Note that cmake when targeting Windows will try to execute @@ -3294,76 +3266,10 @@ fn buildOutputType( }; defer emit_implib_resolved.deinit(); - const main_mod: ?*Package.Module = if (root_src_file) |unresolved_src_path| blk: { - const src_path = try introspect.resolvePath(arena, unresolved_src_path); - if (main_mod_path) |unresolved_main_mod_path| { - const p = try introspect.resolvePath(arena, unresolved_main_mod_path); - break :blk try Package.Module.create(arena, .{ - .root = .{ - .root_dir = Cache.Directory.cwd(), - .sub_path = p, - }, - .root_src_path = if (p.len == 0) - src_path - else - try fs.path.relative(arena, p, src_path), - .fully_qualified_name = "root", - }); - } else { - break :blk try Package.Module.create(arena, .{ - .root = .{ - .root_dir = Cache.Directory.cwd(), - .sub_path = fs.path.dirname(src_path) orelse "", - }, - .root_src_path = fs.path.basename(src_path), - .fully_qualified_name = "root", - }); - } - } else null; - - // Transfer packages added with --deps to the root package - if (main_mod) |mod| { - var it = ModuleDepIterator.init(root_deps_str orelse ""); - while (it.next()) |dep| { - if (dep.expose.len == 0) { - fatal("root module depends on '{s}' with a blank name", .{dep.name}); - } - - for ([_][]const u8{ "std", "root", "builtin" }) |name| { - if (mem.eql(u8, dep.expose, name)) { - fatal("unable to add module '{s}' under name '{s}': conflicts with builtin module", .{ dep.name, dep.expose }); - } - } - - const dep_mod = modules.get(dep.name) orelse - fatal("root module depends on module '{s}' which does not exist", .{dep.name}); - - try mod.deps.put(arena, dep.expose, dep_mod.mod); - } - } - var thread_pool: ThreadPool = undefined; try thread_pool.init(.{ .allocator = gpa }); defer thread_pool.deinit(); - var global_cache_directory: Compilation.Directory = l: { - if (override_global_cache_dir) |p| { - break :l .{ - .handle = try fs.cwd().makeOpenPath(p, .{}), - .path = p, - }; - } - if (builtin.os.tag == .wasi) { - break :l getWasiPreopen("/cache"); - } - const p = try introspect.resolveGlobalCacheDir(arena); - break :l .{ - .handle = try fs.cwd().makeOpenPath(p, .{}), - .path = p, - }; - }; - defer global_cache_directory.handle.close(); - var cleanup_local_cache_dir: ?fs.Dir = null; defer if (cleanup_local_cache_dir) |*dir| dir.close(); @@ -3379,37 +3285,37 @@ fn buildOutputType( if (arg_mode == .run) { break :l global_cache_directory; } - if (main_mod != null) { - // search upwards from cwd until we find directory with build.zig - const cwd_path = try process.getCwdAlloc(arena); - const zig_cache = "zig-cache"; - var dirname: []const u8 = cwd_path; - while (true) { - const joined_path = try fs.path.join(arena, &.{ - dirname, Package.build_zig_basename, - }); - if (fs.cwd().access(joined_path, .{})) |_| { - const cache_dir_path = try fs.path.join(arena, &.{ dirname, zig_cache }); - const dir = try fs.cwd().makeOpenPath(cache_dir_path, .{}); - cleanup_local_cache_dir = dir; - break :l .{ .handle = dir, .path = cache_dir_path }; - } else |err| switch (err) { - error.FileNotFound => { - dirname = fs.path.dirname(dirname) orelse { - break :l global_cache_directory; - }; - continue; - }, - else => break :l global_cache_directory, - } + + // search upwards from cwd until we find directory with build.zig + const cwd_path = try process.getCwdAlloc(arena); + const zig_cache = "zig-cache"; + var dirname: []const u8 = cwd_path; + while (true) { + const joined_path = try fs.path.join(arena, &.{ + dirname, Package.build_zig_basename, + }); + if (fs.cwd().access(joined_path, .{})) |_| { + const cache_dir_path = try fs.path.join(arena, &.{ dirname, zig_cache }); + const dir = try fs.cwd().makeOpenPath(cache_dir_path, .{}); + cleanup_local_cache_dir = dir; + break :l .{ .handle = dir, .path = cache_dir_path }; + } else |err| switch (err) { + error.FileNotFound => { + dirname = fs.path.dirname(dirname) orelse { + break :l global_cache_directory; + }; + continue; + }, + else => break :l global_cache_directory, } } + // Otherwise we really don't have a reasonable place to put the local cache directory, // so we utilize the global one. break :l global_cache_directory; }; - for (c_source_files.items) |*src| { + for (create_module.c_source_files.items) |*src| { if (!mem.eql(u8, src.src_path, "-")) continue; const ext = src.ext orelse @@ -3452,13 +3358,14 @@ fn buildOutputType( .zig_lib_directory = zig_lib_directory, .local_cache_directory = local_cache_directory, .global_cache_directory = global_cache_directory, + .thread_pool = &thread_pool, + .self_exe_path = self_exe_path, + .config = create_module.resolved_options, .root_name = root_name, - .target = target, - .is_native_os = target_query.isNativeOs(), - .is_native_abi = target_query.isNativeAbi(), .sysroot = sysroot, - .output_mode = output_mode, .main_mod = main_mod, + .root_mod = root_mod, + .std_mod = std_mod, .emit_bin = emit_bin_loc, .emit_h = emit_h_resolved.data, .emit_asm = emit_asm_resolved.data, @@ -3466,43 +3373,22 @@ fn buildOutputType( .emit_llvm_bc = emit_llvm_bc_resolved.data, .emit_docs = emit_docs_resolved.data, .emit_implib = emit_implib_resolved.data, - .link_mode = link_mode, .dll_export_fns = dll_export_fns, - .optimize_mode = optimize_mode, .keep_source_files_loaded = false, - .clang_argv = clang_argv.items, .lib_dirs = lib_dirs.items, .rpath_list = rpath_list.items, .symbol_wrap_set = symbol_wrap_set, - .c_source_files = c_source_files.items, - .rc_source_files = rc_source_files.items, + .c_source_files = create_module.c_source_files.items, + .rc_source_files = create_module.rc_source_files.items, .manifest_file = manifest_file, .rc_includes = rc_includes, .link_objects = link_objects.items, .framework_dirs = framework_dirs.items, .frameworks = resolved_frameworks.items, - .system_lib_names = resolved_system_libs.items(.name), - .system_lib_infos = resolved_system_libs.items(.lib), - .wasi_emulated_libs = wasi_emulated_libs.items, - .link_libc = link_libc, - .link_libcpp = link_libcpp, - .link_libunwind = link_libunwind, - .want_pic = want_pic, - .want_pie = want_pie, - .want_lto = want_lto, - .want_unwind_tables = want_unwind_tables, - .want_sanitize_c = want_sanitize_c, - .want_stack_check = want_stack_check, - .want_stack_protector = want_stack_protector, - .want_red_zone = want_red_zone, - .omit_frame_pointer = omit_frame_pointer, - .want_valgrind = want_valgrind, - .want_tsan = want_tsan, + .system_lib_names = create_module.resolved_system_libs.items(.name), + .system_lib_infos = create_module.resolved_system_libs.items(.lib), + .wasi_emulated_libs = create_module.wasi_emulated_libs.items, .want_compiler_rt = want_compiler_rt, - .use_llvm = use_llvm, - .use_lib_llvm = use_lib_llvm, - .use_lld = use_lld, - .use_clang = use_clang, .hash_style = hash_style, .rdynamic = rdynamic, .linker_script = linker_script, @@ -3513,14 +3399,11 @@ fn buildOutputType( .linker_gc_sections = linker_gc_sections, .linker_allow_shlib_undefined = linker_allow_shlib_undefined, .linker_bind_global_refs_locally = linker_bind_global_refs_locally, - .linker_import_memory = linker_import_memory, - .linker_export_memory = linker_export_memory, .linker_import_symbols = linker_import_symbols, .linker_import_table = linker_import_table, .linker_export_table = linker_export_table, .linker_initial_memory = linker_initial_memory, .linker_max_memory = linker_max_memory, - .linker_shared_memory = linker_shared_memory, .linker_print_gc_sections = linker_print_gc_sections, .linker_print_icf_sections = linker_print_icf_sections, .linker_print_map = linker_print_map, @@ -3546,18 +3429,13 @@ 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_override = stack_size_override, .image_base_override = image_base_override, - .strip = strip, .formatted_panics = formatted_panics, - .single_threaded = single_threaded, .function_sections = function_sections, .data_sections = data_sections, .no_builtin = no_builtin, - .self_exe_path = self_exe_path, - .thread_pool = &thread_pool, .clang_passthrough_mode = clang_passthrough_mode, .clang_preprocessor_mode = clang_preprocessor_mode, .version = optional_version, @@ -3571,21 +3449,17 @@ fn buildOutputType( .verbose_llvm_bc = verbose_llvm_bc, .verbose_cimport = verbose_cimport, .verbose_llvm_cpu_features = verbose_llvm_cpu_features, - .machine_code_model = machine_code_model, .color = color, .time_report = time_report, .stack_report = stack_report, - .is_test = arg_mode == .zig_test, .each_lib_rpath = each_lib_rpath, .build_id = build_id, - .test_evented_io = test_evented_io, .test_filter = test_filter, .test_name_prefix = test_name_prefix, .test_runner_path = test_runner_path, .disable_lld_caching = !output_to_cache, .subsystem = subsystem, .dwarf_format = dwarf_format, - .wasi_exec_model = wasi_exec_model, .debug_compile_errors = debug_compile_errors, .enable_link_snapshots = enable_link_snapshots, .install_name = install_name, @@ -3595,7 +3469,6 @@ fn buildOutputType( .headerpad_max_install_names = headerpad_max_install_names, .dead_strip_dylibs = dead_strip_dylibs, .reference_trace = reference_trace, - .error_tracing = error_tracing, .pdb_out_path = pdb_out_path, .error_limit = error_limit, .want_structured_cfg = want_structured_cfg, @@ -3702,7 +3575,7 @@ fn buildOutputType( try test_exec_args.appendSlice(&.{ "-I", p }); } - if (link_libc) { + if (create_module.resolved_options.link_libc) { try test_exec_args.append("-lc"); } else if (target.os.tag == .windows) { try test_exec_args.appendSlice(&.{ @@ -3711,14 +3584,15 @@ fn buildOutputType( }); } - if (!mem.eql(u8, target_arch_os_abi, "native")) { + const first_cli_mod = create_module.modules.values()[0]; + if (first_cli_mod.target_arch_os_abi) |triple| { try test_exec_args.append("-target"); - try test_exec_args.append(target_arch_os_abi); + try test_exec_args.append(triple); } - if (target_mcpu) |mcpu| { + if (first_cli_mod.target_mcpu) |mcpu| { try test_exec_args.append(try std.fmt.allocPrint(arena, "-mcpu={s}", .{mcpu})); } - if (target_dynamic_linker) |dl| { + if (create_module.dynamic_linker) |dl| { try test_exec_args.append("--dynamic-linker"); try test_exec_args.append(dl); } @@ -3742,7 +3616,7 @@ fn buildOutputType( &comp_destroyed, all_args, runtime_args_start, - link_libc, + create_module.resolved_options.link_libc, ); } @@ -3750,6 +3624,243 @@ fn buildOutputType( return cleanExit(); } +const CreateModule = struct { + global_cache_directory: Cache.Directory, + modules: std.StringArrayHashMapUnmanaged(CliModule), + opts: Compilation.Config.Options, + dynamic_linker: ?[]const u8, + object_format: ?[]const u8, + /// undefined until createModule() for the root module is called. + resolved_options: Compilation.Config, + + /// This one is used while collecting CLI options. The set of libs is used + /// directly after computing the target and used to compute link_libc, + /// link_libcpp, and then the libraries are filtered into + /// `external_system_libs` and `resolved_system_libs`. + system_libs: std.StringArrayHashMapUnmanaged(SystemLib), + external_system_libs: std.MultiArrayList(struct { + name: []const u8, + info: SystemLib, + }), + resolved_system_libs: std.MultiArrayList(struct { + name: []const u8, + lib: Compilation.SystemLib, + }), + wasi_emulated_libs: std.ArrayListUnmanaged(wasi_libc.CRTFile), + + c_source_files: std.ArrayListUnmanaged(Compilation.CSourceFile), + rc_source_files: std.ArrayListUnmanaged(Compilation.RcSourceFile), + + // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names. + // This array is populated by zig cc frontend and then has to be converted to zig-style + // CPU features. + llvm_m_args: std.ArrayListUnmanaged([]const u8), +}; + +fn createModule( + gpa: Allocator, + arena: Allocator, + create_module: *CreateModule, + index: usize, + parent: ?*Package.Module, + zig_lib_directory: Cache.Directory, +) Allocator.Error!*Package.Module { + const cli_mod = &create_module.modules.values()[index]; + if (cli_mod.resolved) |m| return m; + + const name = create_module.modules.keys()[index]; + + cli_mod.inherited.resolved_target = t: { + // If the target is not overridden, use the parent's target. Of course, + // if this is the root module then we need to proceed to resolve the + // target. + if (cli_mod.target_arch_os_abi == null and + cli_mod.target_mcpu == null and + create_module.dynamic_linker == null and + create_module.object_format == null) + { + if (parent) |p| break :t p.resolved_target; + } + + var target_parse_options: std.Target.Query.ParseOptions = .{ + .arch_os_abi = cli_mod.target_arch_os_abi orelse "native", + .cpu_features = cli_mod.target_mcpu, + .dynamic_linker = create_module.dynamic_linker, + .object_format = create_module.object_format, + }; + + // Before passing the mcpu string in for parsing, we convert any -m flags that were + // passed in via zig cc to zig-style. + if (create_module.llvm_m_args.items.len != 0) { + // If this returns null, we let it fall through to the case below which will + // run the full parse function and do proper error handling. + if (std.Target.Query.parseCpuArch(target_parse_options)) |cpu_arch| { + var llvm_to_zig_name = std.StringHashMap([]const u8).init(gpa); + defer llvm_to_zig_name.deinit(); + + for (cpu_arch.allFeaturesList()) |feature| { + const llvm_name = feature.llvm_name orelse continue; + try llvm_to_zig_name.put(llvm_name, feature.name); + } + + var mcpu_buffer = std.ArrayList(u8).init(gpa); + defer mcpu_buffer.deinit(); + + try mcpu_buffer.appendSlice(cli_mod.target_mcpu orelse "baseline"); + + for (create_module.llvm_m_args.items) |llvm_m_arg| { + if (mem.startsWith(u8, llvm_m_arg, "mno-")) { + const llvm_name = llvm_m_arg["mno-".len..]; + const zig_name = llvm_to_zig_name.get(llvm_name) orelse { + fatal("target architecture {s} has no LLVM CPU feature named '{s}'", .{ + @tagName(cpu_arch), llvm_name, + }); + }; + try mcpu_buffer.append('-'); + try mcpu_buffer.appendSlice(zig_name); + } else if (mem.startsWith(u8, llvm_m_arg, "m")) { + const llvm_name = llvm_m_arg["m".len..]; + const zig_name = llvm_to_zig_name.get(llvm_name) orelse { + fatal("target architecture {s} has no LLVM CPU feature named '{s}'", .{ + @tagName(cpu_arch), llvm_name, + }); + }; + try mcpu_buffer.append('+'); + try mcpu_buffer.appendSlice(zig_name); + } else { + unreachable; + } + } + + const adjusted_target_mcpu = try arena.dupe(u8, mcpu_buffer.items); + std.log.debug("adjusted target_mcpu: {s}", .{adjusted_target_mcpu}); + target_parse_options.cpu_features = adjusted_target_mcpu; + } + } + + const target_query = parseTargetQueryOrReportFatalError(arena, target_parse_options); + const target = resolveTargetQueryOrFatal(target_query); + break :t .{ + .result = target, + .is_native_os = target_query.isNativeOs(), + .is_native_abi = target_query.isNativeAbi(), + }; + }; + + if (parent == null) { + // This block is for initializing the fields of + // `Compilation.Config.Options` that require knowledge of the + // target (which was just now resolved for the root module above). + const resolved_target = cli_mod.inherited.resolved_target.?; + create_module.opts.resolved_target = resolved_target; + create_module.opts.root_optimize_mode = cli_mod.inherited.optimize_mode; + const target = resolved_target.result; + + // First, remove libc, libc++, and compiler_rt libraries from the system libraries list. + // We need to know whether the set of system libraries contains anything besides these + // to decide whether to trigger native path detection logic. + for (create_module.system_libs.keys(), create_module.system_libs.values()) |lib_name, info| { + if (target.is_libc_lib_name(lib_name)) { + create_module.opts.link_libc = true; + continue; + } + if (target.is_libcpp_lib_name(lib_name)) { + create_module.opts.link_libcpp = true; + continue; + } + switch (target_util.classifyCompilerRtLibName(target, lib_name)) { + .none => {}, + .only_libunwind, .both => { + create_module.opts.link_libunwind = true; + continue; + }, + .only_compiler_rt => { + warn("ignoring superfluous library '{s}': this dependency is fulfilled instead by compiler-rt which zig unconditionally provides", .{lib_name}); + continue; + }, + } + + if (target.isMinGW()) { + const exists = mingw.libExists(arena, target, zig_lib_directory, lib_name) catch |err| { + fatal("failed to check zig installation for DLL import libs: {s}", .{ + @errorName(err), + }); + }; + if (exists) { + try create_module.resolved_system_libs.append(arena, .{ + .name = lib_name, + .lib = .{ + .needed = true, + .weak = false, + .path = null, + }, + }); + continue; + } + } + + if (fs.path.isAbsolute(lib_name)) { + fatal("cannot use absolute path as a system library: {s}", .{lib_name}); + } + + if (target.os.tag == .wasi) { + if (wasi_libc.getEmulatedLibCRTFile(lib_name)) |crt_file| { + try create_module.wasi_emulated_libs.append(arena, crt_file); + continue; + } + } + + try create_module.external_system_libs.append(arena, .{ + .name = lib_name, + .info = info, + }); + } + // After this point, external_system_libs is used instead of system_libs. + + create_module.resolved_options = Compilation.Config.resolve(create_module.opts) catch |err| switch (err) { + else => fatal("unable to resolve compilation options: {s}", .{@errorName(err)}), + }; + } + + const mod = Package.Module.create(arena, .{ + .global_cache_directory = create_module.global_cache_directory, + .paths = cli_mod.paths, + .fully_qualified_name = name, + + .cc_argv = cli_mod.cc_argv, + .inherited = cli_mod.inherited, + .global = create_module.resolved_options, + .parent = parent, + .builtin_mod = null, + }) catch |err| switch (err) { + error.ValgrindUnsupportedOnTarget => fatal("unable to create module '{s}': valgrind does not support the selected target CPU architecture", .{name}), + error.TargetRequiresSingleThreaded => fatal("unable to create module '{s}': the selected target does not support multithreading", .{name}), + error.BackendRequiresSingleThreaded => fatal("unable to create module '{s}': the selected machine code backend is limited to single-threaded applications", .{name}), + error.TargetRequiresPic => fatal("unable to create module '{s}': the selected target requires position independent code", .{name}), + error.PieRequiresPic => fatal("unable to create module '{s}': making a Position Independent Executable requires enabling Position Independent Code", .{name}), + error.DynamicLinkingRequiresPic => fatal("unable to create module '{s}': dynamic linking requires enabling Position Independent Code", .{name}), + error.TargetHasNoRedZone => fatal("unable to create module '{s}': the selected target does not have a red zone", .{name}), + error.StackCheckUnsupportedByTarget => fatal("unable to create module '{s}': the selected target does not support stack checking", .{name}), + error.StackProtectorUnsupportedByTarget => fatal("unable to create module '{s}': the selected target does not support stack protection", .{name}), + error.StackProtectorUnavailableWithoutLibC => fatal("unable to create module '{s}': enabling stack protection requires libc", .{name}), + error.OutOfMemory => return error.OutOfMemory, + }; + cli_mod.resolved = mod; + + for (create_module.c_source_files.items[cli_mod.c_source_files_start..cli_mod.c_source_files_end]) |*item| item.owner = mod; + + for (create_module.rc_source_files.items[cli_mod.rc_source_files_start..cli_mod.rc_source_files_end]) |*item| item.owner = mod; + + for (cli_mod.deps) |dep| { + const dep_index = create_module.modules.getIndex(dep.key) orelse + fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key }); + const dep_mod = try createModule(gpa, arena, create_module, dep_index, mod, zig_lib_directory); + try mod.deps.put(arena, dep.key, dep_mod); + } + + return mod; +} + fn saveState(comp: *Compilation, debug_incremental: bool) void { if (debug_incremental) { comp.saveState() catch |err| { @@ -3984,36 +4095,10 @@ fn serveUpdateResults(s: *Server, comp: *Compilation) !void { } } -const ModuleDepIterator = struct { - split: mem.SplitIterator(u8, .scalar), - - fn init(deps_str: []const u8) ModuleDepIterator { - return .{ .split = mem.splitScalar(u8, deps_str, ',') }; - } - - const Dependency = struct { - expose: []const u8, - name: []const u8, - }; - - fn next(it: *ModuleDepIterator) ?Dependency { - if (it.split.buffer.len == 0) return null; // don't return "" for the first iteration on "" - const str = it.split.next() orelse return null; - if (mem.indexOfScalar(u8, str, '=')) |i| { - return .{ - .expose = str[0..i], - .name = str[i + 1 ..], - }; - } else { - return .{ .expose = str, .name = str }; - } - } -}; - fn parseTargetQueryOrReportFatalError( allocator: Allocator, opts: std.Target.Query.ParseOptions, -) !std.Target.Query { +) std.Target.Query { var opts_with_diags = opts; var diags: std.Target.Query.ParseOptions.Diagnostics = .{}; if (opts_with_diags.diagnostics == null) { @@ -4057,7 +4142,9 @@ fn parseTargetQueryOrReportFatalError( } fatal("unknown object format: '{s}'", .{opts.object_format.?}); }, - else => |e| return e, + else => |e| fatal("unable to parse target query '{s}': {s}", .{ + opts.arch_os_abi, @errorName(e), + }), }; } @@ -4667,7 +4754,7 @@ fn detectRcIncludeDirs(arena: Allocator, zig_lib_dir: []const u8, auto_includes: .os_tag = .windows, .abi = .msvc, }; - const target = try std.zig.system.resolveTargetQuery(target_query); + const target = resolveTargetQueryOrFatal(target_query); const is_native_abi = target_query.isNativeAbi(); const detected_libc = Compilation.detectLibCIncludeDirs(arena, zig_lib_dir, target, is_native_abi, true, null) catch |err| { if (cur_includes == .any) { @@ -4695,7 +4782,7 @@ fn detectRcIncludeDirs(arena: Allocator, zig_lib_dir: []const u8, auto_includes: .os_tag = .windows, .abi = .gnu, }; - const target = try std.zig.system.resolveTargetQuery(target_query); + const target = resolveTargetQueryOrFatal(target_query); const is_native_abi = target_query.isNativeAbi(); const detected_libc = try Compilation.detectLibCIncludeDirs(arena, zig_lib_dir, target, is_native_abi, true, null); return .{ @@ -4757,10 +4844,10 @@ pub fn cmdLibC(gpa: Allocator, args: []const []const u8) !void { } } - const target_query = try parseTargetQueryOrReportFatalError(gpa, .{ + const target_query = parseTargetQueryOrReportFatalError(gpa, .{ .arch_os_abi = target_arch_os_abi, }); - const target = try std.zig.system.resolveTargetQuery(target_query); + const target = resolveTargetQueryOrFatal(target_query); if (print_includes) { var arena_state = std.heap.ArenaAllocator.init(gpa); @@ -5024,7 +5111,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi if (!build_options.enable_logging) { warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{}); } else { - try log_scopes.append(gpa, args[i]); + try log_scopes.append(arena, args[i]); } continue; } else if (mem.eql(u8, arg, "--debug-compile-errors")) { @@ -5115,7 +5202,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi gimmeMoreOfThoseSweetSweetFileDescriptors(); const target_query: std.Target.Query = .{}; - const target = try std.zig.system.resolveTargetQuery(target_query); + const target = resolveTargetQueryOrFatal(target_query); const exe_basename = try std.zig.binNameAlloc(arena, .{ .root_name = "build", @@ -5130,29 +5217,80 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi try thread_pool.init(.{ .allocator = gpa }); defer thread_pool.deinit(); - var main_mod: Package.Module = if (override_build_runner) |build_runner_path| - .{ + const main_mod_paths: Package.Module.CreateOptions.Paths = if (override_build_runner) |runner| .{ + .root = .{ + .root_dir = Cache.Directory.cwd(), + .sub_path = fs.path.dirname(runner) orelse "", + }, + .root_src_path = fs.path.basename(runner), + } else .{ + .root = .{ .root_dir = zig_lib_directory }, + .root_src_path = "build_runner.zig", + }; + + const config = try Compilation.Config.resolve(.{ + .output_mode = .Exe, + .resolved_target = .{ + .result = target, + .is_native_os = true, + .is_native_abi = true, + }, + .have_zcu = true, + .emit_bin = true, + .is_test = false, + }); + + const root_mod = try Package.Module.create(arena, .{ + .global_cache_directory = global_cache_directory, + .paths = main_mod_paths, + .fully_qualified_name = "root", + .cc_argv = &.{}, + .inherited = .{}, + .global = config, + .parent = null, + .builtin_mod = null, + }); + + const builtin_mod = root_mod.getBuiltinDependency(); + const std_mod = try Package.Module.create(arena, .{ + .global_cache_directory = global_cache_directory, + .paths = .{ .root = .{ - .root_dir = Cache.Directory.cwd(), - .sub_path = fs.path.dirname(build_runner_path) orelse "", + .root_dir = zig_lib_directory, + .sub_path = "std", }, - .root_src_path = fs.path.basename(build_runner_path), - .fully_qualified_name = "root", - } - else - .{ - .root = .{ .root_dir = zig_lib_directory }, - .root_src_path = "build_runner.zig", - .fully_qualified_name = "root", - }; + .root_src_path = "std.zig", + }, + .fully_qualified_name = "std", + .cc_argv = &.{}, + .inherited = .{}, + .global = config, + .parent = root_mod, + .builtin_mod = builtin_mod, + }); - var build_mod: Package.Module = .{ - .root = .{ .root_dir = build_root.directory }, - .root_src_path = build_root.build_zig_basename, + const build_mod = try Package.Module.create(arena, .{ + .global_cache_directory = global_cache_directory, + .paths = .{ + .root = .{ .root_dir = build_root.directory }, + .root_src_path = build_root.build_zig_basename, + }, .fully_qualified_name = "root.@build", - }; + .cc_argv = &.{}, + .inherited = .{}, + .global = config, + .parent = root_mod, + .builtin_mod = builtin_mod, + }); if (build_options.only_core_functionality) { - try createEmptyDependenciesModule(arena, &main_mod, local_cache_directory); + try createEmptyDependenciesModule( + arena, + root_mod, + global_cache_directory, + local_cache_directory, + builtin_mod, + config, + ); } else { var http_client: std.http.Client = .{ .allocator = gpa }; defer http_client.deinit(); @@ -5196,7 +5334,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi .has_build_zig = true, .oom_flag = false, - .module = &build_mod, + .module = build_mod, }; job_queue.all_fetches.appendAssumeCapacity(&fetch); @@ -5225,8 +5363,11 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi const deps_mod = try createDependenciesModule( arena, source_buf.items, - &main_mod, + root_mod, + global_cache_directory, local_cache_directory, + builtin_mod, + config, ); { @@ -5242,13 +5383,21 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi if (!f.has_build_zig) continue; const m = try Package.Module.create(arena, .{ - .root = try f.package_root.clone(arena), - .root_src_path = Package.build_zig_basename, + .global_cache_directory = global_cache_directory, + .paths = .{ + .root = try f.package_root.clone(arena), + .root_src_path = Package.build_zig_basename, + }, .fully_qualified_name = try std.fmt.allocPrint( arena, "root.@dependencies.{s}", .{&hash}, ), + .cc_argv = &.{}, + .inherited = .{}, + .global = config, + .parent = root_mod, + .builtin_mod = builtin_mod, }); const hash_cloned = try arena.dupe(u8, &hash); deps_mod.deps.putAssumeCapacityNoClobber(hash_cloned, m); @@ -5276,21 +5425,19 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi } } - try main_mod.deps.put(arena, "@build", &build_mod); + try root_mod.deps.put(arena, "@build", build_mod); const comp = Compilation.create(gpa, .{ .zig_lib_directory = zig_lib_directory, .local_cache_directory = local_cache_directory, .global_cache_directory = global_cache_directory, .root_name = "build", - .target = target, - .is_native_os = target_query.isNativeOs(), - .is_native_abi = target_query.isNativeAbi(), - .output_mode = .Exe, - .main_mod = &main_mod, + .config = config, + .root_mod = root_mod, + .main_mod = build_mod, + .std_mod = std_mod, .emit_bin = emit_bin, .emit_h = null, - .optimize_mode = .Debug, .self_exe_path = self_exe_path, .thread_pool = &thread_pool, .verbose_cc = verbose_cc, @@ -5514,7 +5661,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void .root_decl = .none, }; - file.mod = try Package.Module.create(arena, .{ + file.mod = try Package.Module.createLimited(arena, .{ .root = Package.Path.cwd(), .root_src_path = file.sub_file_path, .fully_qualified_name = "root", @@ -5724,7 +5871,7 @@ fn fmtPathFile( .root_decl = .none, }; - file.mod = try Package.Module.create(fmt.arena, .{ + file.mod = try Package.Module.createLimited(fmt.arena, .{ .root = Package.Path.cwd(), .root_src_path = file.sub_file_path, .fully_qualified_name = "root", @@ -5804,15 +5951,13 @@ pub fn putAstErrorsIntoBundle( .tree = tree, .tree_loaded = true, .zir = undefined, - .mod = undefined, + .mod = try Package.Module.createLimited(gpa, .{ + .root = Package.Path.cwd(), + .root_src_path = path, + .fully_qualified_name = "root", + }), .root_decl = .none, }; - - file.mod = try Package.Module.create(gpa, .{ - .root = Package.Path.cwd(), - .root_src_path = file.sub_file_path, - .fully_qualified_name = "root", - }); defer gpa.destroy(file.mod); file.zir = try AstGen.generate(gpa, file.tree); @@ -6373,7 +6518,7 @@ pub fn cmdAstCheck( file.stat.size = source.len; } - file.mod = try Package.Module.create(arena, .{ + file.mod = try Package.Module.createLimited(arena, .{ .root = Package.Path.cwd(), .root_src_path = file.sub_file_path, .fully_qualified_name = "root", @@ -6546,7 +6691,7 @@ pub fn cmdChangelist( .root_decl = .none, }; - file.mod = try Package.Module.create(arena, .{ + file.mod = try Package.Module.createLimited(arena, .{ .root = Package.Path.cwd(), .root_src_path = file.sub_file_path, .fully_qualified_name = "root", @@ -6669,7 +6814,7 @@ fn warnAboutForeignBinaries( link_libc: bool, ) !void { const host_query: std.Target.Query = .{}; - const host_target = try std.zig.system.resolveTargetQuery(host_query); + const host_target = resolveTargetQueryOrFatal(host_query); switch (std.zig.system.getExternalExecutor(host_target, target, .{ .link_libc = link_libc })) { .native => return, @@ -6809,18 +6954,22 @@ fn parseSubSystem(next_arg: []const u8) !std.Target.SubSystem { /// Silently ignore superfluous search dirs. /// Warn when a dir is added to multiple searchlists. const ClangSearchSanitizer = struct { - argv: *std.ArrayList([]const u8), - map: std.StringHashMap(Membership), + map: std.StringHashMapUnmanaged(Membership) = .{}, - fn init(gpa: Allocator, argv: *std.ArrayList([]const u8)) @This() { - return .{ - .argv = argv, - .map = std.StringHashMap(Membership).init(gpa), - }; + fn reset(self: *@This()) void { + self.map.clearRetainingCapacity(); } - fn addIncludePath(self: *@This(), group: Group, arg: []const u8, dir: []const u8, joined: bool) !void { - const gopr = try self.map.getOrPut(dir); + fn addIncludePath( + self: *@This(), + ally: Allocator, + argv: *std.ArrayListUnmanaged([]const u8), + group: Group, + arg: []const u8, + dir: []const u8, + joined: bool, + ) !void { + const gopr = try self.map.getOrPut(ally, dir); const m = gopr.value_ptr; if (!gopr.found_existing) { // init empty membership @@ -6867,8 +7016,9 @@ const ClangSearchSanitizer = struct { if (m.iwithsysroot) warn(wtxt, .{ dir, "iframeworkwithsysroot", "iwithsysroot" }); }, } - try self.argv.append(arg); - if (!joined) try self.argv.append(dir); + try argv.ensureUnusedCapacity(ally, 2); + argv.appendAssumeCapacity(arg); + if (!joined) argv.appendAssumeCapacity(dir); } const Group = enum { I, isystem, iwithsysroot, idirafter, iframework, iframeworkwithsysroot }; @@ -7244,11 +7394,22 @@ fn cmdFetch( fn createEmptyDependenciesModule( arena: Allocator, main_mod: *Package.Module, + global_cache_directory: Cache.Directory, local_cache_directory: Cache.Directory, + builtin_mod: *Package.Module, + global_options: Compilation.Config, ) !void { var source = std.ArrayList(u8).init(arena); try Package.Fetch.JobQueue.createEmptyDependenciesSource(&source); - _ = try createDependenciesModule(arena, source.items, main_mod, local_cache_directory); + _ = try createDependenciesModule( + arena, + source.items, + main_mod, + global_cache_directory, + local_cache_directory, + builtin_mod, + global_options, + ); } /// Creates the dependencies.zig file and corresponding `Package.Module` for the @@ -7257,7 +7418,10 @@ fn createDependenciesModule( arena: Allocator, source: []const u8, main_mod: *Package.Module, + global_cache_directory: Cache.Directory, local_cache_directory: Cache.Directory, + builtin_mod: *Package.Module, + global_options: Compilation.Config, ) !*Package.Module { // Atomically create the file in a directory named after the hash of its contents. const basename = "dependencies.zig"; @@ -7283,25 +7447,25 @@ fn createDependenciesModule( ); const deps_mod = try Package.Module.create(arena, .{ - .root = .{ - .root_dir = local_cache_directory, - .sub_path = o_dir_sub_path, + .global_cache_directory = global_cache_directory, + .paths = .{ + .root = .{ + .root_dir = local_cache_directory, + .sub_path = o_dir_sub_path, + }, + .root_src_path = basename, }, - .root_src_path = basename, .fully_qualified_name = "root.@dependencies", + .parent = main_mod, + .builtin_mod = builtin_mod, + .cc_argv = &.{}, + .inherited = .{}, + .global = global_options, }); try main_mod.deps.put(arena, "@dependencies", deps_mod); return deps_mod; } -fn defaultWasmEntryName(exec_model: ?std.builtin.WasiExecModel) []const u8 { - const model = exec_model orelse .command; - if (model == .reactor) { - return "_initialize"; - } - return "_start"; -} - const BuildRoot = struct { directory: Cache.Directory, build_zig_basename: []const u8, @@ -7509,3 +7673,18 @@ fn findTemplates(gpa: Allocator, arena: Allocator) Templates { .buffer = std.ArrayList(u8).init(gpa), }; } + +fn parseOptimizeMode(s: []const u8) std.builtin.OptimizeMode { + return std.meta.stringToEnum(std.builtin.OptimizeMode, s) orelse + fatal("unrecognized optimization mode: '{s}'", .{s}); +} + +fn parseWasiExecModel(s: []const u8) std.builtin.WasiExecModel { + return std.meta.stringToEnum(std.builtin.WasiExecModel, s) orelse + fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{s}); +} + +fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target { + return std.zig.system.resolveTargetQuery(target_query) catch |err| + fatal("unable to resolve target: {s}", .{@errorName(err)}); +} diff --git a/src/target.zig b/src/target.zig index 624a67d043f01ce0999dc2d1993281cb4ad55ad9..f610e55fb04cbecd35dc97e26a12ecfa3a69b61b 100644 --- a/src/target.zig +++ b/src/target.zig @@ -3,6 +3,8 @@ const Type = @import("type.zig").Type; const AddressSpace = std.builtin.AddressSpace; const Alignment = @import("InternPool.zig").Alignment; +pub const default_stack_protector_buffer_size = 4; + pub const ArchOsAbi = struct { arch: std.Target.Cpu.Arch, os: std.Target.Os.Tag, @@ -204,11 +206,18 @@ pub fn supports_fpic(target: std.Target) bool { return target.os.tag != .windows and target.os.tag != .uefi; } -pub fn isSingleThreaded(target: std.Target) bool { +pub fn alwaysSingleThreaded(target: std.Target) bool { _ = target; return false; } +pub fn defaultSingleThreaded(target: std.Target) bool { + return switch (target.cpu.arch) { + .wasm32, .wasm64 => true, + else => false, + }; +} + /// Valgrind supports more, but Zig does not support them yet. pub fn hasValgrindSupport(target: std.Target) bool { switch (target.cpu.arch) { @@ -375,12 +384,17 @@ pub fn classifyCompilerRtLibName(target: std.Target, name: []const u8) CompilerR } pub fn hasDebugInfo(target: std.Target) bool { - if (target.cpu.arch.isNvptx()) { - // TODO: not sure how to test "ptx >= 7.5" with featureset - return std.Target.nvptx.featureSetHas(target.cpu.features, .ptx75); - } - - return true; + return switch (target.cpu.arch) { + .nvptx, .nvptx64 => std.Target.nvptx.featureSetHas(target.cpu.features, .ptx75) or + std.Target.nvptx.featureSetHas(target.cpu.features, .ptx76) or + std.Target.nvptx.featureSetHas(target.cpu.features, .ptx77) or + std.Target.nvptx.featureSetHas(target.cpu.features, .ptx78) or + std.Target.nvptx.featureSetHas(target.cpu.features, .ptx80) or + std.Target.nvptx.featureSetHas(target.cpu.features, .ptx81), + .wasm32, .wasm64 => false, + .bpfel, .bpfeb => false, + else => true, + }; } pub fn defaultCompilerRtOptimizeMode(target: std.Target) std.builtin.OptimizeMode { @@ -619,3 +633,36 @@ pub fn fnCallConvAllowsZigTypes(target: std.Target, cc: std.builtin.CallingConve else => false, }; } + +pub fn zigBackend(target: std.Target, use_llvm: bool) std.builtin.CompilerBackend { + if (use_llvm) return .stage2_llvm; + if (target.ofmt == .c) return .stage2_c; + return switch (target.cpu.arch) { + .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, + .arm, .armeb, .thumb, .thumbeb => .stage2_arm, + .x86_64 => .stage2_x86_64, + .x86 => .stage2_x86, + .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, + .riscv64 => .stage2_riscv64, + .sparc64 => .stage2_sparc64, + .spirv64 => .stage2_spirv64, + else => .other, + }; +} + +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 => "_start", + .wasm => switch (wasi_exec_model) { + .reactor => "_initialize", + .command => "_start", + }, + else => null, + }; +}