From b42442f5b4913938b04f580b1d13a1fd7318514d Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sun, 13 Nov 2022 03:42:51 -0500 Subject: [PATCH] windows: fixes to support using zig cc/c++ with CMake on Windows Using zig cc with CMake on Windows was failing during compiler detection. -nostdinc was causing the crt not to be linked, and Coff/lld.zig assumed that wWinMainCRTStartup would be present in this case. -nostdlib did not prevent the default behaviour of linking libc++ when zig c++ was used. This caused libc++ to be built when CMake ran ABI detection using zig c++, which fails as libcxxabi cannot compile under MSVC. - Change the behaviour of COFF -nostdinc to set /entry to the function that the default CRT method for the specified subsystem would have called. - Fix -ENTRY being passed twice if it was specified explicitly and -nostdlib was present. - Add support for /pdb, /version, /implib, and /subsystem as linker args (passed by CMake) - Remove -Ddisable-zstd, no longer needed - Add -Ddisable-libcpp for use when bootstrapping on msvc --- CMakeLists.txt | 1 + build.zig | 13 +++++-------- src/Compilation.zig | 7 +++++++ src/link.zig | 3 +++ src/link/Coff/lld.zig | 35 ++++++++++++++++++++++++++++++++--- src/main.zig | 25 ++++++++++++++++++++++++- 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7340572bdc..a92878781b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ set(ZIG_SHARED_LLVM off CACHE BOOL "Prefer linking against shared LLVM libraries set(ZIG_STATIC_LLVM off CACHE BOOL "Prefer linking against static LLVM libraries") set(ZIG_STATIC_ZLIB off CACHE BOOL "Prefer linking against static zlib") set(ZIG_ENABLE_ZSTD on CACHE BOOL "Enable linking zstd") +set(ZIG_ENABLE_LIBCPP on CACHE BOOL "Enable linking libcpp") set(ZIG_STATIC_ZSTD off CACHE BOOL "Prefer linking against static zstd") set(ZIG_USE_CCACHE off CACHE BOOL "Use ccache") diff --git a/build.zig b/build.zig index 1c814e203a..6eba66db42 100644 --- a/build.zig +++ b/build.zig @@ -99,7 +99,7 @@ pub fn build(b: *Builder) !void { const enable_macos_sdk = b.option(bool, "enable-macos-sdk", "Run tests requiring presence of macOS SDK and frameworks") orelse false; const enable_symlinks_windows = b.option(bool, "enable-symlinks-windows", "Run tests requiring presence of symlinks on Windows") orelse false; const config_h_path_option = b.option([]const u8, "config_h", "Path to the generated config.h"); - const disable_zstd = b.option(bool, "disable-zstd", "Skip linking zstd") orelse false; + const disable_libcpp = b.option(bool, "disable-libcpp", "Skip building/linking libcpp") orelse false; if (!skip_install_lib_files) { b.installDirectory(InstallDirectoryOptions{ @@ -278,8 +278,8 @@ pub fn build(b: *Builder) !void { try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx); } else { // Here we are -Denable-llvm but no cmake integration. - try addStaticLlvmOptionsToExe(exe, !disable_zstd); - try addStaticLlvmOptionsToExe(test_cases, !disable_zstd); + try addStaticLlvmOptionsToExe(exe); + try addStaticLlvmOptionsToExe(test_cases); } if (target.isWindows()) { inline for (.{ exe, test_cases }) |artifact| { @@ -607,7 +607,7 @@ fn addCmakeCfgOptionsToExe( } } -fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep, link_zstd: bool) !void { +fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep) !void { // Adds the Zig C++ sources which both stage1 and stage2 need. // // We need this because otherwise zig_clang_cc1_main.cpp ends up pulling @@ -629,10 +629,7 @@ fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep, link_zstd: bool) !vo } exe.linkSystemLibrary("z"); - - if (link_zstd) { - exe.linkSystemLibrary("zstd"); - } + exe.linkSystemLibrary("zstd"); if (exe.target.getOs().tag != .windows or exe.target.getAbi() != .msvc) { // This means we rely on clang-or-zig-built LLVM, Clang, LLD libraries. diff --git a/src/Compilation.zig b/src/Compilation.zig index aa352fb9ab..5d0a9308af 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1042,6 +1042,11 @@ pub const InitOptions = struct { /// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols dead_strip_dylibs: bool = false, libcxx_abi_version: libcxx.AbiVersion = libcxx.AbiVersion.default, + /// (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, }; fn addPackageTableToCacheHash( @@ -1892,6 +1897,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .headerpad_max_install_names = options.headerpad_max_install_names, .dead_strip_dylibs = options.dead_strip_dylibs, .force_undefined_symbols = .{}, + .pdb_source_path = pdb_source_path, + .pdb_out_path = options.pdb_out_path, }); errdefer bin_file.destroy(); comp.* = .{ diff --git a/src/link.zig b/src/link.zig index ee9efd83ed..222c0e4918 100644 --- a/src/link.zig +++ b/src/link.zig @@ -223,6 +223,9 @@ pub const Options = struct { /// 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, diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig index be6c90d11d..4f1124115e 100644 --- a/src/link/Coff/lld.zig +++ b/src/link/Coff/lld.zig @@ -166,12 +166,16 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod try argv.append("-DEBUG"); const out_ext = std.fs.path.extension(full_out_path); - const out_pdb = try allocPrint(arena, "{s}.pdb", .{ + const out_pdb = self.base.options.pdb_out_path orelse try allocPrint(arena, "{s}.pdb", .{ full_out_path[0 .. full_out_path.len - out_ext.len], }); + try argv.append(try allocPrint(arena, "-PDB:{s}", .{out_pdb})); try argv.append(try allocPrint(arena, "-PDBALTPATH:{s}", .{out_pdb})); } + if (self.base.options.version) |version| { + try argv.append(try allocPrint(arena, "-VERSION:{}.{}", .{ version.major, version.minor })); + } if (self.base.options.lto) { switch (self.base.options.optimize_mode) { .Debug => {}, @@ -427,7 +431,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod } } else { try argv.append("-NODEFAULTLIB"); - if (!is_lib) { + if (!is_lib and self.base.options.entry == null) { if (self.base.options.module) |module| { if (module.stage1_flags.have_winmain_crt_startup) { try argv.append("-ENTRY:WinMainCRTStartup"); @@ -435,7 +439,32 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod try argv.append("-ENTRY:wWinMainCRTStartup"); } } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); + // If the crt isn't being linked, it won't provide the CRT startup methods that + // call through to the user-provided entrypoint. Instead, choose the entry point + // that the CRT methods would have called. Note that this differs from the behaviour + // of link.exe (which still tries to use the CRT methods in this case), but this + // fixes CMake compiler checks when using zig cc on Windows, as Windows-Clang.cmake + // does not specify /entry:main + + // TODO: I think the correct thing to do in this case would be to inspect the object + // being linked (like link.exe / lld-link does) and detect which symbols are available. + // This would allow detection of the w variants, as well as the crt methods. + if (resolved_subsystem) |subsystem| { + switch (subsystem) { + .Console => { + // The default is to call mainCRTStartup/wmainCRTStartup, which calls main/wmain + try argv.append("-ENTRY:main"); + }, + .Windows => { + // The default is to call WinMainCRTStartup/wWinMainCRTStartup, which calls WinMain/wWinMain + try argv.append("-ENTRY:WinMain"); + }, + else => {} + } + } + + // when no /entry is specified, lld-link will infer it based on which functions + // are present in the object being linked - see lld/COFF/Driver.cpp#LinkerDriver::findDefaultEntry } } } diff --git a/src/main.zig b/src/main.zig index d718f299c7..5e2377637c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -782,6 +782,7 @@ fn buildOutputType( var headerpad_max_install_names: bool = false; var dead_strip_dylibs: bool = false; var reference_trace: ?u32 = null; + var pdb_out_path: ?[]const u8 = 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 @@ -1541,7 +1542,10 @@ fn buildOutputType( .no_stack_protector => want_stack_protector = 0, .unwind_tables => want_unwind_tables = true, .no_unwind_tables => want_unwind_tables = false, - .nostdlib => ensure_libc_on_non_freestanding = false, + .nostdlib => { + ensure_libc_on_non_freestanding = false; + ensure_libcpp_on_non_freestanding = false; + }, .nostdlib_cpp => ensure_libcpp_on_non_freestanding = false, .shared => { link_mode = .Dynamic; @@ -2120,6 +2124,24 @@ fn buildOutputType( next_arg, }); }; + } else if (mem.startsWith(u8, arg, "/subsystem:")) { + var split_it = mem.splitBackwards(u8, arg, ":"); + subsystem = try parseSubSystem(split_it.first()); + } else if (mem.startsWith(u8, arg, "/implib:")) { + var split_it = mem.splitBackwards(u8, arg, ":"); + emit_implib = .{ .yes = split_it.first() }; + emit_implib_arg_provided = true; + } else if (mem.startsWith(u8, arg, "/pdb:")) { + var split_it = mem.splitBackwards(u8, arg, ":"); + pdb_out_path = split_it.first(); + } else if (mem.startsWith(u8, arg, "/version:")) { + var split_it = mem.splitBackwards(u8, arg, ":"); + const version_arg = split_it.first(); + version = std.builtin.Version.parse(version_arg) catch |err| { + fatal("unable to parse /version '{s}': {s}", .{ arg, @errorName(err) }); + }; + + have_version = true; } else { warn("unsupported linker arg: {s}", .{arg}); } @@ -3069,6 +3091,7 @@ fn buildOutputType( .headerpad_max_install_names = headerpad_max_install_names, .dead_strip_dylibs = dead_strip_dylibs, .reference_trace = reference_trace, + .pdb_out_path = pdb_out_path, }) catch |err| switch (err) { error.LibCUnavailable => { const target = target_info.target;