diff --git a/build.zig b/build.zig index cbc530e877..0ff5285201 100644 --- a/build.zig +++ b/build.zig @@ -573,13 +573,17 @@ fn addCmakeCfgOptionsToExe( exe.linkLibCpp(); } else { const need_cpp_includes = true; + const lib_suffix = switch (cfg.llvm_linkage) { + .static => exe.target.staticLibSuffix()[1..], + .dynamic => exe.target.dynamicLibSuffix()[1..], + }; // System -lc++ must be used because in this code path we are attempting to link // against system-provided LLVM, Clang, LLD. if (exe.target.getOsTag() == .linux) { - // First we try to static link against gcc libstdc++. If that doesn't work, - // we fall back to -lc++ and cross our fingers. - addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) { + // First we try to link against gcc libstdc++. If that doesn't work, we fall + // back to -lc++ and cross our fingers. + addCxxKnownPath(b, cfg, exe, b.fmt("libstdc++.{s}", .{lib_suffix}), "", need_cpp_includes) catch |err| switch (err) { error.RequiredLibraryNotFound => { exe.linkSystemLibrary("c++"); }, @@ -587,11 +591,11 @@ fn addCmakeCfgOptionsToExe( }; exe.linkSystemLibrary("unwind"); } else if (exe.target.isFreeBSD()) { - try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); + try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes); exe.linkSystemLibrary("pthread"); } else if (exe.target.getOsTag() == .openbsd) { - try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); - try addCxxKnownPath(b, cfg, exe, "libc++abi.a", null, need_cpp_includes); + try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes); + try addCxxKnownPath(b, cfg, exe, b.fmt("libc++abi.{s}", .{lib_suffix}), null, need_cpp_includes); } else if (exe.target.isDarwin()) { exe.linkSystemLibrary("c++"); } diff --git a/src/clang.zig b/src/clang.zig index ef90ddebce..40dacc5df8 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -1913,3 +1913,6 @@ extern fn ZigClangLoadFromCommandLine( errors_len: *usize, resources_path: [*:0]const u8, ) ?*ASTUnit; + +pub const isLLVMUsingSeparateLibcxx = ZigClangIsLLVMUsingSeparateLibcxx; +extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 3e9b1ab3ed..917e4c18d1 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1592,6 +1592,15 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v } } } + for (self.base.options.objects) |obj| { + if (Compilation.classifyFileExt(obj.path) == .shared_library) { + const lib_dir_path = std.fs.path.dirname(obj.path).?; + if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) { + try argv.append("-rpath"); + try argv.append(lib_dir_path); + } + } + } } for (self.base.options.lib_dirs) |lib_dir| { diff --git a/src/main.zig b/src/main.zig index f2291a0646..f192137b3c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -174,6 +174,17 @@ pub fn main() anyerror!void { return mainArgs(gpa, arena, args); } +/// Check that LLVM and Clang have been linked properly so that they are using the same +/// libc++ and can safely share objects with pointers to static variables in libc++ +fn verifyLibcxxCorrectlyLinked() void { + if (build_options.have_llvm and ZigClangIsLLVMUsingSeparateLibcxx()) { + fatal( + \\Zig was built/linked incorrectly: LLVM and Clang have separate copies of libc++ + \\ If you are dynamically linking LLVM, make sure you dynamically link libc++ too + , .{}); + } +} + pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { if (args.len <= 1) { std.log.info("{s}", .{usage}); @@ -261,8 +272,12 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi const stdout = io.getStdOut().writer(); return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); } else if (mem.eql(u8, cmd, "version")) { - return std.io.getStdOut().writeAll(build_options.version ++ "\n"); + try std.io.getStdOut().writeAll(build_options.version ++ "\n"); + // Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version" + // to avoid affecting the startup time for build-critical commands (check takes about ~10 μs) + return verifyLibcxxCorrectlyLinked(); } else if (mem.eql(u8, cmd, "env")) { + verifyLibcxxCorrectlyLinked(); return @import("print_env.zig").cmdEnv(arena, cmd_args, io.getStdOut().writer()); } else if (mem.eql(u8, cmd, "zen")) { return io.getStdOut().writeAll(info_zen); @@ -4487,6 +4502,8 @@ pub const info_zen = \\ ; +extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool; + extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; extern "c" fn ZigLlvmAr_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 1c0ff7bf09..7b79f8e985 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -3432,3 +3432,31 @@ const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct Zi const llvm::APSInt *result = &casted->getInitVal(); return reinterpret_cast(result); } + +// Get a pointer to a static variable in libc++ from LLVM and make sure that +// it matches our own. +// +// This check is needed because if static/dynamic linking is mixed incorrectly, +// it's possible for Clang and LLVM to end up with duplicate "copies" of libc++. +// +// This is not benign: Static variables are not shared, so equality comparisons +// that depend on pointers to static variables will fail. One such failure is +// std::generic_category(), which causes POSIX error codes to compare as unequal +// when passed between LLVM and Clang. +// +// See also: https://github.com/ziglang/zig/issues/11168 +bool ZigClangIsLLVMUsingSeparateLibcxx() { + + // Temporarily create an InMemoryFileSystem, so that we can perform a file + // lookup that is guaranteed to fail. + auto FS = new llvm::vfs::InMemoryFileSystem(true); + auto StatusOrErr = FS->status("foo.txt"); + delete FS; + + // This should return a POSIX (generic_category) error code, but if LLVM has + // its own copy of libc++ this will actually be a separate category instance. + assert(!StatusOrErr); + auto EC = StatusOrErr.getError(); + return EC.category() != std::generic_category(); +} + diff --git a/src/zig_clang.h b/src/zig_clang.h index 6babb21bfe..3da57d4301 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -1418,4 +1418,5 @@ ZIG_EXTERN_C const struct ZigClangRecordDecl *ZigClangFieldDecl_getParent(const ZIG_EXTERN_C unsigned ZigClangFieldDecl_getFieldIndex(const struct ZigClangFieldDecl *); ZIG_EXTERN_C const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct ZigClangEnumConstantDecl *); +ZIG_EXTERN_C bool ZigClangIsLLVMUsingSeparateLibcxx(); #endif