From a950cb42bdf7b3f339071868c6675e7dcc892bae Mon Sep 17 00:00:00 2001 From: Kurt Kartaltepe Date: Fri, 9 Jul 2021 20:41:35 -0700 Subject: [PATCH 1/5] Coff linker: Add IMPLIB support Allow --out-implib and -implib as passed by cmake and meson to be correctly passed through to the linker to generate import libraries. --- src/Compilation.zig | 6 +++++- src/link.zig | 1 + src/link/Coff.zig | 5 +++++ src/main.zig | 29 +++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 48899f6f6e9c6696e24c956f6478f628dec0cdff..61533eebb0f4b4e6234c0dd54a2a3e80ed142601 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -767,6 +767,8 @@ pub const InitOptions = struct { test_filter: ?[]const u8 = null, test_name_prefix: ?[]const u8 = null, subsystem: ?std.Target.SubSystem = null, + /// Windows/PE only. Where to output the import library, can contain directories. + out_implib: ?[]const u8 = 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. @@ -946,7 +948,8 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { options.output_mode == .Lib or options.lld_argv.len != 0 or options.image_base_override != null or - options.linker_script != null or options.version_script != null) + options.linker_script != null or options.version_script != null or + options.out_implib != null) { break :blk true; } @@ -1461,6 +1464,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os, .disable_lld_caching = options.disable_lld_caching, .subsystem = options.subsystem, + .out_implib = options.out_implib, .is_test = options.is_test, .wasi_exec_model = wasi_exec_model, .use_stage1 = use_stage1, diff --git a/src/link.zig b/src/link.zig index 4ad5952767e4310f9bf131f67edd7b87292ff64a..76f50a78fe352279b552faff45ec447db1586cf3 100644 --- a/src/link.zig +++ b/src/link.zig @@ -127,6 +127,7 @@ pub const Options = struct { gc_sections: ?bool = null, allow_shlib_undefined: ?bool, subsystem: ?std.Target.SubSystem, + out_implib: ?[]const u8, linker_script: ?[]const u8, version_script: ?[]const u8, soname: ?[]const u8, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index b6f32797792ba6cbacb29a0900f2f79ac8d3d276..ee59f4f034081bf3d46176d07f12dee1de4e6b47 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -948,6 +948,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { man.hash.add(self.base.options.dynamicbase); man.hash.addOptional(self.base.options.major_subsystem_version); man.hash.addOptional(self.base.options.minor_subsystem_version); + man.hash.addOptionalBytes(self.base.options.out_implib); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -1094,6 +1095,10 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append(p); } + if (self.base.options.out_implib != null) { + try argv.append(try allocPrint(arena, "-IMPLIB:{s}.lib", .{full_out_path})); + } + const resolved_subsystem: ?std.Target.SubSystem = blk: { if (self.base.options.subsystem) |explicit| break :blk explicit; switch (target.os.tag) { diff --git a/src/main.zig b/src/main.zig index 0b274b3aee1403964e6db7c49c60fcce1e766da7..4900743677b53be4913a111e5364f1fe39237d15 100644 --- a/src/main.zig +++ b/src/main.zig @@ -654,6 +654,7 @@ fn buildOutputType( var main_pkg_path: ?[]const u8 = null; var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.Target.SubSystem = null; + var out_implib: ?[]const u8 = null; var major_subsystem_version: ?u32 = null; var minor_subsystem_version: ?u32 = null; var wasi_exec_model: ?std.builtin.WasiExecModel = null; @@ -1637,6 +1638,14 @@ fn buildOutputType( fatal("unable to parse -current_version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); }; have_version = true; + } else if (mem.eql(u8, arg, "--out-implib") or + mem.eql(u8, arg, "-implib")) + { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + out_implib = linker_args.items[i]; } else { warn("unsupported linker arg: {s}", .{arg}); } @@ -2154,6 +2163,16 @@ fn buildOutputType( else => false, }; + // Always output import libraries (.lib) when building for msvc to replicate + // `link` behavior. lld does not always output import libraries so on the + // gnu abi users must set out_implib. + if (output_mode == .Lib and emit_bin == .yes and target_info.target.abi == .msvc and out_implib == null) { + const emit_bin_ext = fs.path.extension(emit_bin.yes); + out_implib = try std.fmt.allocPrint(gpa, "{s}.lib", .{ + emit_bin.yes[0 .. emit_bin.yes.len - emit_bin_ext.len], + }); + } + gimmeMoreOfThoseSweetSweetFileDescriptors(); const comp = Compilation.create(gpa, .{ @@ -2263,6 +2282,7 @@ fn buildOutputType( .test_name_prefix = test_name_prefix, .disable_lld_caching = !have_enable_cache, .subsystem = subsystem, + .out_implib = out_implib, .wasi_exec_model = wasi_exec_model, .debug_compile_errors = debug_compile_errors, .enable_link_snapshots = enable_link_snapshots, @@ -2661,6 +2681,15 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi _ = try cache_dir.updateFile(src_pdb_path, cwd, dst_pdb_path, .{}); } + + if (comp.bin_file.options.out_implib) |out_implib| { + const src_implib_path = try std.fmt.allocPrint(gpa, "{s}.lib", .{bin_sub_path}); + defer gpa.free(src_implib_path); + if (std.fs.path.dirname(out_implib)) |implib_dir| { + try cwd.makePath(implib_dir); + } + _ = try cache_dir.updateFile(src_implib_path, cwd, out_implib, .{}); + } }, } } -- 2.54.0 From fd369bcb0b67bde0072f1549be2fd0b34014703f Mon Sep 17 00:00:00 2001 From: Kurt Kartaltepe Date: Fri, 9 Jul 2021 21:29:43 -0700 Subject: [PATCH 2/5] Coff Linker: Pass extra lld args Previously these were added to the hash but not actually appended to the linker arguments. --- src/link/Coff.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/link/Coff.zig b/src/link/Coff.zig index ee59f4f034081bf3d46176d07f12dee1de4e6b47..c9feebe7a21358002b44d16813f31d158a19b563 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1057,6 +1057,9 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { if (self.base.options.dynamicbase) { try argv.append("-dynamicbase"); } + + try argv.appendSlice(self.base.options.extra_lld_args); + const subsystem_suffix = ss: { if (self.base.options.major_subsystem_version) |major| { if (self.base.options.minor_subsystem_version) |minor| { -- 2.54.0 From 7e23b3245a9bf6e002009e6c18c10a9995671afa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Nov 2021 17:11:37 -0700 Subject: [PATCH 3/5] stage2: remove extra_lld_args This mechanism for sending arbitrary linker args to LLD has no place in the Zig frontend, because our goal is for the frontend to understand all the arguments and not treat linker args like a black box. For example we have self-hosted linking in addition to LLD, so we want to have the options make sense to both linking codepaths, not just the LLD one. Passing -O linker args will now result in a warning that the arg does nothing. --- src/Compilation.zig | 3 --- src/link.zig | 2 -- src/link/Coff.zig | 3 --- src/link/Elf.zig | 3 --- src/link/Wasm.zig | 1 - src/main.zig | 14 +++++++++----- 6 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 61533eebb0f4b4e6234c0dd54a2a3e80ed142601..c84fd2c96d4a365c2035a1dda34e1b4da06d2211 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -667,7 +667,6 @@ pub const InitOptions = struct { optimize_mode: std.builtin.Mode = .Debug, keep_source_files_loaded: bool = false, clang_argv: []const []const u8 = &[0][]const u8{}, - lld_argv: []const []const u8 = &[0][]const u8{}, lib_dirs: []const []const u8 = &[0][]const u8{}, rpath_list: []const []const u8 = &[0][]const u8{}, c_source_files: []const CSourceFile = &[0]CSourceFile{}, @@ -946,7 +945,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { link_eh_frame_hdr or options.link_emit_relocs or options.output_mode == .Lib or - options.lld_argv.len != 0 or options.image_base_override != null or options.linker_script != null or options.version_script != null or options.out_implib != null) @@ -1440,7 +1438,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .eh_frame_hdr = link_eh_frame_hdr, .emit_relocs = options.link_emit_relocs, .rdynamic = options.rdynamic, - .extra_lld_args = options.lld_argv, .soname = options.soname, .version = options.version, .compatibility_version = options.compatibility_version, diff --git a/src/link.zig b/src/link.zig index 76f50a78fe352279b552faff45ec447db1586cf3..f00d7809f4e13198fa1677049ba50e775c30d0ba 100644 --- a/src/link.zig +++ b/src/link.zig @@ -132,8 +132,6 @@ pub const Options = struct { version_script: ?[]const u8, soname: ?[]const u8, llvm_cpu_features: ?[*:0]const u8, - /// Extra args passed directly to LLD. Ignored when not linking with LLD. - extra_lld_args: []const []const u8, objects: []const []const u8, framework_dirs: []const []const u8, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index c9feebe7a21358002b44d16813f31d158a19b563..19a99a2e329e6f1a0e85ee18803fbd9aa18bf92d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -927,7 +927,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try man.addOptionalFile(module_obj_path); man.hash.addOptional(self.base.options.stack_size_override); man.hash.addOptional(self.base.options.image_base_override); - man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.addListOfBytes(self.base.options.lib_dirs); man.hash.add(self.base.options.skip_linker_dependencies); if (self.base.options.link_libc) { @@ -1058,8 +1057,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append("-dynamicbase"); } - try argv.appendSlice(self.base.options.extra_lld_args); - const subsystem_suffix = ss: { if (self.base.options.major_subsystem_version) |major| { if (self.base.options.minor_subsystem_version) |minor| { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index cc7d5ba0e5f0665de74aabacaaa4e1face1630de..43447e0ec4ff95cdc105e802c18b3725fbee6765 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1322,7 +1322,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { man.hash.add(self.base.options.eh_frame_hdr); man.hash.add(self.base.options.emit_relocs); man.hash.add(self.base.options.rdynamic); - man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.addListOfBytes(self.base.options.lib_dirs); man.hash.addListOfBytes(self.base.options.rpath_list); man.hash.add(self.base.options.each_lib_rpath); @@ -1461,8 +1460,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { try argv.append("--export-dynamic"); } - try argv.appendSlice(self.base.options.extra_lld_args); - if (self.base.options.z_nodelete) { try argv.append("-z"); try argv.append("nodelete"); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index cf05fcd94a68c3b39cdc419f58e417e05b7685a7..fd6e042f9a30b59421852f1dfbf778068996b3fb 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -714,7 +714,6 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { try man.addOptionalFile(module_obj_path); try man.addOptionalFile(compiler_rt_path); man.hash.addOptional(self.base.options.stack_size_override); - man.hash.addListOfBytes(self.base.options.extra_lld_args); man.hash.add(self.base.options.import_memory); man.hash.addOptional(self.base.options.initial_memory); man.hash.addOptional(self.base.options.max_memory); diff --git a/src/main.zig b/src/main.zig index 4900743677b53be4913a111e5364f1fe39237d15..7d8b9b1265c0069360267d4bfd22efc51b7944a2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -672,9 +672,6 @@ fn buildOutputType( var extra_cflags = std.ArrayList([]const u8).init(gpa); defer extra_cflags.deinit(); - var lld_argv = std.ArrayList([]const u8).init(gpa); - defer lld_argv.deinit(); - var lib_dirs = std.ArrayList([]const u8).init(gpa); defer lib_dirs.deinit(); @@ -1474,8 +1471,16 @@ fn buildOutputType( fatal("expected linker arg after '{s}'", .{arg}); } version_script = linker_args.items[i]; + } else if (mem.eql(u8, arg, "-O")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + warn("ignoring linker arg -O{s} because it does nothing", .{ + linker_args.items[i], + }); } else if (mem.startsWith(u8, arg, "-O")) { - try lld_argv.append(arg); + warn("ignoring linker arg {s} because it does nothing", .{arg}); } else if (mem.eql(u8, arg, "--gc-sections")) { linker_gc_sections = true; } else if (mem.eql(u8, arg, "--no-gc-sections")) { @@ -2200,7 +2205,6 @@ fn buildOutputType( .optimize_mode = optimize_mode, .keep_source_files_loaded = false, .clang_argv = clang_argv.items, - .lld_argv = lld_argv.items, .lib_dirs = lib_dirs.items, .rpath_list = rpath_list.items, .c_source_files = c_source_files.items, -- 2.54.0 From 27c5c7fb23fceb0a333444408a1dea4188a14c32 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Nov 2021 18:08:56 -0700 Subject: [PATCH 4/5] stage2: proper `-femit-implib` frontend support * Improve the logic for determining whether emitting an import lib is eligible, and improve the error message when the user provides contradictory arguments. * Integrate with the EmitLoc / Emit system that already exists, and use the `-femit-implib[=path]`/`-fno-emit-implib` convention that already exists. * Proper integration with the caching system. * CLI: fix bug in error reporting for resolving EmitLoc values for other parameters. --- src/Compilation.zig | 30 +++++++++-- src/link.zig | 3 +- src/link/Coff.zig | 11 ++-- src/main.zig | 123 ++++++++++++++++++++++++++++++-------------- 4 files changed, 117 insertions(+), 50 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index c84fd2c96d4a365c2035a1dda34e1b4da06d2211..2ff390f19671fe86142972ab6608525fccec1bba 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -654,6 +654,8 @@ pub const InitOptions = struct { emit_analysis: ?EmitLoc = null, /// `null` means to not emit docs. 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 @@ -766,8 +768,6 @@ pub const InitOptions = struct { test_filter: ?[]const u8 = null, test_name_prefix: ?[]const u8 = null, subsystem: ?std.Target.SubSystem = null, - /// Windows/PE only. Where to output the import library, can contain directories. - out_implib: ?[]const u8 = 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. @@ -947,7 +947,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { options.output_mode == .Lib or options.image_base_override != null or options.linker_script != null or options.version_script != null or - options.out_implib != null) + options.emit_implib != null) { break :blk true; } @@ -1183,6 +1183,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { cache.hash.add(options.output_mode); cache.hash.add(options.machine_code_model); cache.hash.addOptionalEmitLoc(options.emit_bin); + cache.hash.addOptionalEmitLoc(options.emit_implib); cache.hash.addBytes(options.root_name); if (options.target.os.tag == .wasi) cache.hash.add(wasi_exec_model); // TODO audit this and make sure everything is in it @@ -1339,18 +1340,21 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const bin_file_emit: ?link.Emit = blk: { const emit_bin = options.emit_bin orelse break :blk null; + if (emit_bin.directory) |directory| { break :blk link.Emit{ .directory = directory, .sub_path = emit_bin.basename, }; } + if (module) |zm| { break :blk link.Emit{ .directory = zm.zig_cache_artifact_directory, .sub_path = emit_bin.basename, }; } + // We could use the cache hash as is no problem, however, we increase // the likelihood of cache hits by adding the first C source file // path name (not contents) to the hash. This way if the user is compiling @@ -1377,6 +1381,24 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; }; + const implib_emit: ?link.Emit = blk: { + const emit_implib = options.emit_implib orelse break :blk null; + + if (emit_implib.directory) |directory| { + break :blk link.Emit{ + .directory = directory, + .sub_path = emit_implib.basename, + }; + } + + // 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{ + .directory = bin_file_emit.?.directory, + .sub_path = emit_implib.basename, + }; + }; + var system_libs: std.StringArrayHashMapUnmanaged(SystemLib) = .{}; errdefer system_libs.deinit(gpa); try system_libs.ensureTotalCapacity(gpa, options.system_lib_names.len); @@ -1386,6 +1408,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const bin_file = try link.File.openPath(gpa, .{ .emit = bin_file_emit, + .implib_emit = implib_emit, .root_name = root_name, .module = module, .target = options.target, @@ -1461,7 +1484,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os, .disable_lld_caching = options.disable_lld_caching, .subsystem = options.subsystem, - .out_implib = options.out_implib, .is_test = options.is_test, .wasi_exec_model = wasi_exec_model, .use_stage1 = use_stage1, diff --git a/src/link.zig b/src/link.zig index f00d7809f4e13198fa1677049ba50e775c30d0ba..36573975001f840c29188ec8592869f6f0db9aba 100644 --- a/src/link.zig +++ b/src/link.zig @@ -47,6 +47,8 @@ pub const Options = struct { /// This is `null` when -fno-emit-bin is used. When `openPath` or `flush` is called, /// it will have already been null-checked. emit: ?Emit, + /// This is `null` not building a Windows DLL, or when -fno-emit-implib is used. + implib_emit: ?Emit, target: std.Target, output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, @@ -127,7 +129,6 @@ pub const Options = struct { gc_sections: ?bool = null, allow_shlib_undefined: ?bool, subsystem: ?std.Target.SubSystem, - out_implib: ?[]const u8, linker_script: ?[]const u8, version_script: ?[]const u8, soname: ?[]const u8, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 19a99a2e329e6f1a0e85ee18803fbd9aa18bf92d..55bd912bef5238726ba90ef00116f445965fa7f7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -947,7 +947,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { man.hash.add(self.base.options.dynamicbase); man.hash.addOptional(self.base.options.major_subsystem_version); man.hash.addOptional(self.base.options.minor_subsystem_version); - man.hash.addOptionalBytes(self.base.options.out_implib); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -978,7 +977,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { } const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - if (self.base.options.output_mode == .Obj) { // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy // here. TODO: think carefully about how we can avoid this redundant operation when doing @@ -1070,6 +1068,11 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + if (self.base.options.implib_emit) |emit| { + const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + if (self.base.options.link_libc) { if (self.base.options.libc_installation) |libc_installation| { try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); @@ -1095,10 +1098,6 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append(p); } - if (self.base.options.out_implib != null) { - try argv.append(try allocPrint(arena, "-IMPLIB:{s}.lib", .{full_out_path})); - } - const resolved_subsystem: ?std.Target.SubSystem = blk: { if (self.base.options.subsystem) |explicit| break :blk explicit; switch (target.os.tag) { diff --git a/src/main.zig b/src/main.zig index 7d8b9b1265c0069360267d4bfd22efc51b7944a2..2c99f508d4888c744626ac9c49bca216c3e74d00 100644 --- a/src/main.zig +++ b/src/main.zig @@ -317,6 +317,8 @@ const usage_build_generic = \\ -fno-emit-docs (default) Do not produce docs/ dir with html documentation \\ -femit-analysis[=path] Write analysis JSON file with type information \\ -fno-emit-analysis (default) Do not write analysis JSON file with type information + \\ -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL + \\ -fno-emit-implib Do not produce an import .lib when building a Windows DLL \\ --show-builtin Output the source of @import("builtin") then exit \\ --cache-dir [path] Override the local cache directory \\ --global-cache-dir [path] Override the global cache directory @@ -585,6 +587,8 @@ fn buildOutputType( var emit_llvm_bc: Emit = .no; var emit_docs: Emit = .no; var emit_analysis: 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_mcpu: ?[]const u8 = null; var target_dynamic_linker: ?[]const u8 = null; @@ -654,7 +658,6 @@ fn buildOutputType( var main_pkg_path: ?[]const u8 = null; var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.Target.SubSystem = null; - var out_implib: ?[]const u8 = null; var major_subsystem_version: ?u32 = null; var minor_subsystem_version: ?u32 = null; var wasi_exec_model: ?std.builtin.WasiExecModel = null; @@ -1091,6 +1094,15 @@ fn buildOutputType( emit_analysis = .{ .yes = arg["-femit-analysis=".len..] }; } else if (mem.eql(u8, arg, "-fno-emit-analysis")) { emit_analysis = .no; + } else if (mem.eql(u8, arg, "-femit-implib")) { + emit_implib = .yes_default_path; + emit_implib_arg_provided = true; + } else if (mem.startsWith(u8, arg, "-femit-implib=")) { + emit_implib = .{ .yes = arg["-femit-implib=".len..] }; + emit_implib_arg_provided = true; + } else if (mem.eql(u8, arg, "-fno-emit-implib")) { + emit_implib = .no; + emit_implib_arg_provided = true; } else if (mem.eql(u8, arg, "-dynamic")) { link_mode = .Dynamic; } else if (mem.eql(u8, arg, "-static")) { @@ -1650,7 +1662,8 @@ fn buildOutputType( if (i >= linker_args.items.len) { fatal("expected linker arg after '{s}'", .{arg}); } - out_implib = linker_args.items[i]; + emit_implib = .{ .yes = linker_args.items[i] }; + emit_implib_arg_provided = true; } else { warn("unsupported linker arg: {s}", .{arg}); } @@ -1998,11 +2011,15 @@ fn buildOutputType( const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name}); var emit_h_resolved = emit_h.resolve(default_h_basename) catch |err| { switch (emit_h) { - .yes => { - fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ emit_h.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_h_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_h_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2012,11 +2029,15 @@ fn buildOutputType( const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name}); var emit_asm_resolved = emit_asm.resolve(default_asm_basename) catch |err| { switch (emit_asm) { - .yes => { - fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ emit_asm.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_asm_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_asm_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2026,11 +2047,15 @@ fn buildOutputType( const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name}); var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename) catch |err| { switch (emit_llvm_ir) { - .yes => { - fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ emit_llvm_ir.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_ir_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_llvm_ir_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2040,11 +2065,15 @@ fn buildOutputType( const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name}); var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename) catch |err| { switch (emit_llvm_bc) { - .yes => { - fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ emit_llvm_bc.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ default_llvm_bc_basename, @errorName(err) }); + fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{ + default_llvm_bc_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2054,11 +2083,15 @@ fn buildOutputType( const default_analysis_basename = try std.fmt.allocPrint(arena, "{s}-analysis.json", .{root_name}); var emit_analysis_resolved = emit_analysis.resolve(default_analysis_basename) catch |err| { switch (emit_analysis) { - .yes => { - fatal("unable to open directory from argument 'femit-analysis', '{s}': {s}", .{ emit_analysis.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-analysis', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { - fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ default_analysis_basename, @errorName(err) }); + fatal("unable to open directory from arguments 'name' or 'soname', '{s}': {s}", .{ + default_analysis_basename, @errorName(err), + }); }, .no => unreachable, } @@ -2067,8 +2100,10 @@ fn buildOutputType( var emit_docs_resolved = emit_docs.resolve("docs") catch |err| { switch (emit_docs) { - .yes => { - fatal("unable to open directory from argument 'femit-docs', '{s}': {s}", .{ emit_h.yes, @errorName(err) }); + .yes => |p| { + fatal("unable to open directory from argument '-femit-docs', '{s}': {s}", .{ + p, @errorName(err), + }); }, .yes_default_path => { fatal("unable to open directory 'docs': {s}", .{@errorName(err)}); @@ -2078,6 +2113,35 @@ fn buildOutputType( }; defer emit_docs_resolved.deinit(); + const is_dyn_lib = switch (output_mode) { + .Obj, .Exe => false, + .Lib => (link_mode orelse .Static) == .Dynamic, + }; + const implib_eligible = is_dyn_lib and + emit_bin_loc != null and target_info.target.os.tag == .windows; + if (!implib_eligible) { + if (!emit_implib_arg_provided) { + emit_implib = .no; + } else if (emit_implib != .no) { + fatal("the argument -femit-implib is allowed only when building a Windows DLL", .{}); + } + } + const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name}); + var emit_implib_resolved = emit_implib.resolve(default_implib_basename) catch |err| { + switch (emit_implib) { + .yes => |p| { + fatal("unable to open directory from argument '-femit-implib', '{s}': {s}", .{ + p, @errorName(err), + }); + }, + .yes_default_path => { + fatal("unable to open directory 'docs': {s}", .{@errorName(err)}); + }, + .no => unreachable, + } + }; + defer emit_implib_resolved.deinit(); + const main_pkg: ?*Package = if (root_src_file) |src_path| blk: { if (main_pkg_path) |p| { const rel_src_path = try fs.path.relative(gpa, p, src_path); @@ -2168,16 +2232,6 @@ fn buildOutputType( else => false, }; - // Always output import libraries (.lib) when building for msvc to replicate - // `link` behavior. lld does not always output import libraries so on the - // gnu abi users must set out_implib. - if (output_mode == .Lib and emit_bin == .yes and target_info.target.abi == .msvc and out_implib == null) { - const emit_bin_ext = fs.path.extension(emit_bin.yes); - out_implib = try std.fmt.allocPrint(gpa, "{s}.lib", .{ - emit_bin.yes[0 .. emit_bin.yes.len - emit_bin_ext.len], - }); - } - gimmeMoreOfThoseSweetSweetFileDescriptors(); const comp = Compilation.create(gpa, .{ @@ -2199,6 +2253,7 @@ fn buildOutputType( .emit_llvm_bc = emit_llvm_bc_resolved.data, .emit_docs = emit_docs_resolved.data, .emit_analysis = emit_analysis_resolved.data, + .emit_implib = emit_implib_resolved.data, .link_mode = link_mode, .dll_export_fns = dll_export_fns, .object_format = object_format, @@ -2286,7 +2341,6 @@ fn buildOutputType( .test_name_prefix = test_name_prefix, .disable_lld_caching = !have_enable_cache, .subsystem = subsystem, - .out_implib = out_implib, .wasi_exec_model = wasi_exec_model, .debug_compile_errors = debug_compile_errors, .enable_link_snapshots = enable_link_snapshots, @@ -2685,15 +2739,6 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi _ = try cache_dir.updateFile(src_pdb_path, cwd, dst_pdb_path, .{}); } - - if (comp.bin_file.options.out_implib) |out_implib| { - const src_implib_path = try std.fmt.allocPrint(gpa, "{s}.lib", .{bin_sub_path}); - defer gpa.free(src_implib_path); - if (std.fs.path.dirname(out_implib)) |implib_dir| { - try cwd.makePath(implib_dir); - } - _ = try cache_dir.updateFile(src_implib_path, cwd, out_implib, .{}); - } }, } } -- 2.54.0 From 20cc7af8e6e47ba209ab0d462826f40516c86b9d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Nov 2021 18:35:37 -0700 Subject: [PATCH 5/5] stage2: support LLD -O flags on ELF In 7e23b3245a9bf6e002009e6c18c10a9995671afa I made -O flags to the linker emit a warning that the argument does nothing. That was not correct however; LLD does have some logic that does different things depending on -O0, -O1, and -O2. It defaults to -O1, and it does less optimizations with -O0 and more with -O2. With this commit, e.g. `-Wl,-O1` is supported by the `zig cc` frontend, and by default we pass `-O0` to LLD in debug mode, and `-O3` in release modes. I also fixed a bug in the LLD ELF linker line which was incorrectly passing `-O` flags instead of `--lto-O` flags for LTO. --- src/Compilation.zig | 6 +++ src/link.zig | 1 + src/link/Elf.zig | 8 +++- src/main.zig | 12 ++++-- test/standalone/install_raw_hex/build.zig | 48 +++++++++++------------ 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 2ff390f19671fe86142972ab6608525fccec1bba..4196eb634d86ac545204982ad0fb0f8b8b33ee9c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -736,6 +736,7 @@ pub const InitOptions = struct { linker_tsaware: bool = false, linker_nxcompat: bool = false, linker_dynamicbase: bool = false, + linker_optimization: ?u8 = null, major_subsystem_version: ?u32 = null, minor_subsystem_version: ?u32 = null, clang_passthrough_mode: bool = false, @@ -1140,6 +1141,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const strip = options.strip or !target_util.hasDebugInfo(options.target); 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) { + .Debug => @as(u8, 0), + else => @as(u8, 3), + }; // We put everything into the cache hash that *cannot be modified during an incremental update*. // For example, one cannot change the target between updates, but one can change source files, @@ -1450,6 +1455,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .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, .stack_size_override = options.stack_size_override, diff --git a/src/link.zig b/src/link.zig index 36573975001f840c29188ec8592869f6f0db9aba..f4c51c735fb154091c96c39fa066c6f17d13c5e5 100644 --- a/src/link.zig +++ b/src/link.zig @@ -99,6 +99,7 @@ pub const Options = struct { tsaware: bool, nxcompat: bool, dynamicbase: bool, + linker_optimization: u8, bind_global_refs_locally: bool, import_memory: bool, initial_memory: ?u64, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 43447e0ec4ff95cdc105e802c18b3725fbee6765..646a568684784091d0c2acf7606705330b2a5308 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1349,6 +1349,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { man.hash.add(self.base.options.bind_global_refs_locally); man.hash.add(self.base.options.tsan); man.hash.addOptionalBytes(self.base.options.sysroot); + man.hash.add(self.base.options.linker_optimization); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -1425,10 +1426,13 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (self.base.options.lto) { switch (self.base.options.optimize_mode) { .Debug => {}, - .ReleaseSmall => try argv.append("-O2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + .ReleaseSmall => try argv.append("--lto-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"), } } + try argv.append(try std.fmt.allocPrint(arena, "-O{d}", .{ + self.base.options.linker_optimization, + })); if (self.base.options.output_mode == .Exe) { try argv.append("-z"); diff --git a/src/main.zig b/src/main.zig index 2c99f508d4888c744626ac9c49bca216c3e74d00..c2e7d293ec6a8622dbbd82e637335865c98ec5f2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -635,6 +635,7 @@ fn buildOutputType( var linker_tsaware = false; var linker_nxcompat = false; var linker_dynamicbase = false; + var linker_optimization: ?u8 = null; var test_evented_io = false; var test_no_exec = false; var stack_size_override: ?u64 = null; @@ -1488,11 +1489,13 @@ fn buildOutputType( if (i >= linker_args.items.len) { fatal("expected linker arg after '{s}'", .{arg}); } - warn("ignoring linker arg -O{s} because it does nothing", .{ - linker_args.items[i], - }); + linker_optimization = std.fmt.parseUnsigned(u8, linker_args.items[i], 10) catch |err| { + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); + }; } else if (mem.startsWith(u8, arg, "-O")) { - warn("ignoring linker arg {s} because it does nothing", .{arg}); + linker_optimization = std.fmt.parseUnsigned(u8, arg["-O".len..], 10) catch |err| { + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); + }; } else if (mem.eql(u8, arg, "--gc-sections")) { linker_gc_sections = true; } else if (mem.eql(u8, arg, "--no-gc-sections")) { @@ -2309,6 +2312,7 @@ fn buildOutputType( .linker_tsaware = linker_tsaware, .linker_nxcompat = linker_nxcompat, .linker_dynamicbase = linker_dynamicbase, + .linker_optimization = linker_optimization, .major_subsystem_version = major_subsystem_version, .minor_subsystem_version = minor_subsystem_version, .link_eh_frame_hdr = link_eh_frame_hdr, diff --git a/test/standalone/install_raw_hex/build.zig b/test/standalone/install_raw_hex/build.zig index 086fafebbca57f7cc285de2b565f18961d8a6400..789197b62759633fc8e23c921e36d0596583e589 100644 --- a/test/standalone/install_raw_hex/build.zig +++ b/test/standalone/install_raw_hex/build.zig @@ -35,7 +35,7 @@ pub fn build(b: *Builder) void { ":1001140000000000000000000000000000000000DB", ":1001240000000000000000000000000000000000CB", ":1001340000000000000000000000000000000000BB", - ":10014400830202006C020100090000002102010088", + ":1001440083020200F401010009000000FE01010025", ":100154000900000001000000000000000000000091", ":1001640000080002008001010004000010000000EB", ":100174000000000000000000000000001F0000005C", @@ -44,17 +44,17 @@ pub fn build(b: *Builder) void { ":1001A40000000000000000001F000000000000002C", ":1001B400000000000000000000000000000000003B", ":1001C400000000000000000000000000000000002B", - ":1001D4005B02010010000000F40101002C0000008B", - ":1001E4003F0201001B0000002B020100130000006D", - ":1001F40072656D61696E646572206469766973699C", - ":100204006F6E206279207A65726F206F72206E653E", - ":100214006761746976652076616C756500636F72D9", - ":100224007465782D6D3400696E646578206F75741B", - ":10023400206F6620626F756E647300696E74656703", - ":1002440065722063617374207472756E6361746582", - ":10025400642062697473006469766973696F6E20DF", - ":100264006279207A65726F00636F727465785F6D6E", - ":100274003400000081B00091FFE700BEFDE7D0B577", + ":1001D4000802010010000000190201002C000000B8", + ":1001E400460201001B00000062020100130000002F", + ":1001F400636F727465785F6D3400636F72746578D1", + ":100204002D6D34006469766973696F6E206279209C", + ":100214007A65726F0072656D61696E6465722064DF", + ":1002240069766973696F6E206279207A65726F20CE", + ":100234006F72206E656761746976652076616C758E", + ":100244006500696E746567657220636173742074F8", + ":1002540072756E6361746564206269747300696E9B", + ":10026400646578206F7574206F6620626F756E64A4", + ":100274007300000081B00091FFE700BEFDE7D0B538", ":1002840002AF90B00391029007A800F029F80399F7", ":100294000020069048680490FFE7049906980190AE", ":1002A40088420FD2FFE7019903980068405C07F881", @@ -89,7 +89,7 @@ pub fn build(b: *Builder) void { ":1001140000000000000000000000000000000000DB", ":1001240000000000000000000000000000000000CB", ":1001340000000000000000000000000000000000BB", - ":10014400830202006C020100090000002102010088", + ":1001440083020200F401010009000000FE01010025", ":100154000900000001000000000000000000000091", ":1001640000080002008001010004000010000000EB", ":100174000000000000000000000000001F0000005C", @@ -98,17 +98,17 @@ pub fn build(b: *Builder) void { ":1001A40000000000000000001F000000000000002C", ":1001B400000000000000000000000000000000003B", ":1001C400000000000000000000000000000000002B", - ":1001D4005B02010010000000F40101002C0000008B", - ":1001E4003F0201001B0000002B020100130000006D", - ":1001F40072656D61696E646572206469766973699C", - ":100204006F6E206279207A65726F206F72206E653E", - ":100214006761746976652076616C756500636F72D9", - ":100224007465782D6D3400696E646578206F75741B", - ":10023400206F6620626F756E647300696E74656703", - ":1002440065722063617374207472756E6361746582", - ":10025400642062697473006469766973696F6E20DF", - ":100264006279207A65726F00636F727465785F6D6E", - ":100274003400000081B00091FFE700BEFDE7D0B577", + ":1001D4000802010010000000190201002C000000B8", + ":1001E400460201001B00000062020100130000002F", + ":1001F400636F727465785F6D3400636F72746578D1", + ":100204002D6D34006469766973696F6E206279209C", + ":100214007A65726F0072656D61696E6465722064DF", + ":1002240069766973696F6E206279207A65726F20CE", + ":100234006F72206E656761746976652076616C758E", + ":100244006500696E746567657220636173742074F8", + ":1002540072756E6361746564206269747300696E9B", + ":10026400646578206F7574206F6620626F756E64A4", + ":100274007300000081B00091FFE700BEFDE7D0B538", ":1002840002AF90B00391029007A800F029F80399F7", ":100294000020069048680490FFE7049906980190AE", ":1002A40088420FD2FFE7019903980068405C07F881", -- 2.54.0