Skip to content

Commit 3cf8f28

Browse files
authored
Merge pull request #12085 from topolarity/dyn-link-libcpp
Dynamically link `libc++` if integrating with system LLVM
2 parents 4fc2acd + 58540f9 commit 3cf8f28

File tree

6 files changed

+69
-7
lines changed

6 files changed

+69
-7
lines changed

build.zig

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,25 +573,29 @@ fn addCmakeCfgOptionsToExe(
573573
exe.linkLibCpp();
574574
} else {
575575
const need_cpp_includes = true;
576+
const lib_suffix = switch (cfg.llvm_linkage) {
577+
.static => exe.target.staticLibSuffix()[1..],
578+
.dynamic => exe.target.dynamicLibSuffix()[1..],
579+
};
576580

577581
// System -lc++ must be used because in this code path we are attempting to link
578582
// against system-provided LLVM, Clang, LLD.
579583
if (exe.target.getOsTag() == .linux) {
580-
// First we try to static link against gcc libstdc++. If that doesn't work,
581-
// we fall back to -lc++ and cross our fingers.
582-
addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) {
584+
// First we try to link against gcc libstdc++. If that doesn't work, we fall
585+
// back to -lc++ and cross our fingers.
586+
addCxxKnownPath(b, cfg, exe, b.fmt("libstdc++.{s}", .{lib_suffix}), "", need_cpp_includes) catch |err| switch (err) {
583587
error.RequiredLibraryNotFound => {
584588
exe.linkSystemLibrary("c++");
585589
},
586590
else => |e| return e,
587591
};
588592
exe.linkSystemLibrary("unwind");
589593
} else if (exe.target.isFreeBSD()) {
590-
try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes);
594+
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes);
591595
exe.linkSystemLibrary("pthread");
592596
} else if (exe.target.getOsTag() == .openbsd) {
593-
try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes);
594-
try addCxxKnownPath(b, cfg, exe, "libc++abi.a", null, need_cpp_includes);
597+
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++.{s}", .{lib_suffix}), null, need_cpp_includes);
598+
try addCxxKnownPath(b, cfg, exe, b.fmt("libc++abi.{s}", .{lib_suffix}), null, need_cpp_includes);
595599
} else if (exe.target.isDarwin()) {
596600
exe.linkSystemLibrary("c++");
597601
}

src/clang.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,3 +1913,6 @@ extern fn ZigClangLoadFromCommandLine(
19131913
errors_len: *usize,
19141914
resources_path: [*:0]const u8,
19151915
) ?*ASTUnit;
1916+
1917+
pub const isLLVMUsingSeparateLibcxx = ZigClangIsLLVMUsingSeparateLibcxx;
1918+
extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool;

src/link/Elf.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,15 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
15921592
}
15931593
}
15941594
}
1595+
for (self.base.options.objects) |obj| {
1596+
if (Compilation.classifyFileExt(obj.path) == .shared_library) {
1597+
const lib_dir_path = std.fs.path.dirname(obj.path).?;
1598+
if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) {
1599+
try argv.append("-rpath");
1600+
try argv.append(lib_dir_path);
1601+
}
1602+
}
1603+
}
15951604
}
15961605

15971606
for (self.base.options.lib_dirs) |lib_dir| {

src/main.zig

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,17 @@ pub fn main() anyerror!void {
174174
return mainArgs(gpa, arena, args);
175175
}
176176

177+
/// Check that LLVM and Clang have been linked properly so that they are using the same
178+
/// libc++ and can safely share objects with pointers to static variables in libc++
179+
fn verifyLibcxxCorrectlyLinked() void {
180+
if (build_options.have_llvm and ZigClangIsLLVMUsingSeparateLibcxx()) {
181+
fatal(
182+
\\Zig was built/linked incorrectly: LLVM and Clang have separate copies of libc++
183+
\\ If you are dynamically linking LLVM, make sure you dynamically link libc++ too
184+
, .{});
185+
}
186+
}
187+
177188
pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
178189
if (args.len <= 1) {
179190
std.log.info("{s}", .{usage});
@@ -261,8 +272,12 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
261272
const stdout = io.getStdOut().writer();
262273
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
263274
} else if (mem.eql(u8, cmd, "version")) {
264-
return std.io.getStdOut().writeAll(build_options.version ++ "\n");
275+
try std.io.getStdOut().writeAll(build_options.version ++ "\n");
276+
// Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version"
277+
// to avoid affecting the startup time for build-critical commands (check takes about ~10 μs)
278+
return verifyLibcxxCorrectlyLinked();
265279
} else if (mem.eql(u8, cmd, "env")) {
280+
verifyLibcxxCorrectlyLinked();
266281
return @import("print_env.zig").cmdEnv(arena, cmd_args, io.getStdOut().writer());
267282
} else if (mem.eql(u8, cmd, "zen")) {
268283
return io.getStdOut().writeAll(info_zen);
@@ -4487,6 +4502,8 @@ pub const info_zen =
44874502
\\
44884503
;
44894504

4505+
extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool;
4506+
44904507
extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
44914508
extern "c" fn ZigLlvmAr_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
44924509

src/zig_clang.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3432,3 +3432,31 @@ const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct Zi
34323432
const llvm::APSInt *result = &casted->getInitVal();
34333433
return reinterpret_cast<const ZigClangAPSInt *>(result);
34343434
}
3435+
3436+
// Get a pointer to a static variable in libc++ from LLVM and make sure that
3437+
// it matches our own.
3438+
//
3439+
// This check is needed because if static/dynamic linking is mixed incorrectly,
3440+
// it's possible for Clang and LLVM to end up with duplicate "copies" of libc++.
3441+
//
3442+
// This is not benign: Static variables are not shared, so equality comparisons
3443+
// that depend on pointers to static variables will fail. One such failure is
3444+
// std::generic_category(), which causes POSIX error codes to compare as unequal
3445+
// when passed between LLVM and Clang.
3446+
//
3447+
// See also: https://github.com/ziglang/zig/issues/11168
3448+
bool ZigClangIsLLVMUsingSeparateLibcxx() {
3449+
3450+
// Temporarily create an InMemoryFileSystem, so that we can perform a file
3451+
// lookup that is guaranteed to fail.
3452+
auto FS = new llvm::vfs::InMemoryFileSystem(true);
3453+
auto StatusOrErr = FS->status("foo.txt");
3454+
delete FS;
3455+
3456+
// This should return a POSIX (generic_category) error code, but if LLVM has
3457+
// its own copy of libc++ this will actually be a separate category instance.
3458+
assert(!StatusOrErr);
3459+
auto EC = StatusOrErr.getError();
3460+
return EC.category() != std::generic_category();
3461+
}
3462+

src/zig_clang.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,4 +1418,5 @@ ZIG_EXTERN_C const struct ZigClangRecordDecl *ZigClangFieldDecl_getParent(const
14181418
ZIG_EXTERN_C unsigned ZigClangFieldDecl_getFieldIndex(const struct ZigClangFieldDecl *);
14191419

14201420
ZIG_EXTERN_C const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct ZigClangEnumConstantDecl *);
1421+
ZIG_EXTERN_C bool ZigClangIsLLVMUsingSeparateLibcxx();
14211422
#endif

0 commit comments

Comments
 (0)