Added _LIBCPP_HAS_NO_THREADS for single_threaded binaries linked with libcxx.

Fixed single-threaded mode for Windows.
This commit is contained in:
Alexander Slesarev 2021-10-31 22:36:30 -06:00 committed by Andrew Kelley
parent 67c4b16d6e
commit 3997828a61
6 changed files with 189 additions and 45 deletions

View File

@ -1,6 +1,7 @@
const std = @import("../std.zig");
const builtin = @import("builtin");
const build = std.build;
const CrossTarget = std.zig.CrossTarget;
const Step = build.Step;
const Builder = build.Builder;
const LibExeObjStep = build.LibExeObjStep;
@ -142,6 +143,23 @@ pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {
self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
}
/// Returns true if the step could be run, otherwise false
pub fn isRunnable(
self: *RunStep,
) bool {
for (self.argv.items) |arg| {
switch (arg) {
.artifact => |artifact| {
_ = self.getExternalExecutor(artifact) catch {
return false;
};
},
else => {},
}
}
return true;
}
pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void {
self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) };
}
@ -154,6 +172,57 @@ fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo {
};
}
fn getExternalExecutor(self: *RunStep, artifact: *LibExeObjStep) !?[]const u8 {
const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;
const executor = self.builder.host.getExternalExecutor(artifact.target_info, .{
.qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null,
.link_libc = artifact.is_linking_libc,
});
switch (executor) {
.bad_dl, .bad_os_or_cpu => {
return error.NoExecutable;
},
.native => {
return null;
},
.rosetta => {
if (self.builder.enable_rosetta) {
return null;
} else {
return error.RosettaNotEnabled;
}
},
.qemu => |bin_name| {
if (self.builder.enable_qemu) {
return bin_name;
} else {
return error.QemuNotEnabled;
}
},
.wine => |bin_name| {
if (self.builder.enable_wine) {
return bin_name;
} else {
return error.WineNotEnabled;
}
},
.wasmtime => |bin_name| {
if (self.builder.enable_wasmtime) {
return bin_name;
} else {
return error.WasmtimeNotEnabled;
}
},
.darling => |bin_name| {
if (self.builder.enable_darling) {
return bin_name;
} else {
return error.DarlingNotEnabled;
}
},
}
}
fn make(step: *Step) !void {
const self = @fieldParentPtr(RunStep, "step", step);
@ -169,6 +238,9 @@ fn make(step: *Step) !void {
// On Windows we don't have rpaths so we have to add .dll search paths to PATH
self.addPathForDynLibs(artifact);
}
if (try self.getExternalExecutor(artifact)) |executor| {
try argv_list.append(executor);
}
const executable_path = artifact.installed_path orelse artifact.getOutputSource().getPath(self.builder);
try argv_list.append(executable_path);
},

View File

@ -1180,6 +1180,15 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
if (must_single_thread and !single_threaded) {
return error.TargetRequiresSingleThreaded;
}
if (!single_threaded and options.link_libcpp) {
if (options.target.cpu.arch.isARM()) {
log.warn(
\\libc++ does not work on multi-threaded ARM yet.
\\For more details: https://github.com/ziglang/zig/issues/6573
, .{});
return error.TargetRequiresSingleThreaded;
}
}
const llvm_cpu_features: ?[*:0]const u8 = if (build_options.have_llvm and use_llvm) blk: {
var buf = std.ArrayList(u8).init(arena);
@ -3803,6 +3812,10 @@ pub fn addCCArgs(
try argv.append("-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS");
try argv.append("-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS");
try argv.append("-D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS");
if (comp.bin_file.options.single_threaded) {
try argv.append("-D_LIBCPP_HAS_NO_THREADS");
} else {}
}
if (comp.bin_file.options.link_libunwind) {

View File

@ -128,6 +128,12 @@ pub fn buildLibCXX(comp: *Compilation) !void {
continue;
if (std.mem.startsWith(u8, cxx_src, "src/support/ibm/") and target.os.tag != .zos)
continue;
if (comp.bin_file.options.single_threaded) {
if (std.mem.startsWith(u8, cxx_src, "src/support/win32/thread_win32.cpp")) {
continue;
}
try cflags.append("-D_LIBCPP_HAS_NO_THREADS");
}
try cflags.append("-DNDEBUG");
try cflags.append("-D_LIBCPP_BUILDING_LIBRARY");
@ -145,8 +151,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {
}
if (target.os.tag == .wasi) {
// WASI doesn't support thread and exception yet.
try cflags.append("-D_LIBCPP_HAS_NO_THREADS");
// WASI doesn't support exceptions yet.
try cflags.append("-fno-exceptions");
}
@ -264,13 +269,20 @@ pub fn buildLibCXXABI(comp: *Compilation) !void {
var cflags = std.ArrayList([]const u8).init(arena);
if (target.os.tag == .wasi) {
// WASI doesn't support thread and exception yet.
if (std.mem.startsWith(u8, cxxabi_src, "src/cxa_thread_atexit.cpp") or
std.mem.startsWith(u8, cxxabi_src, "src/cxa_exception.cpp") or
// WASI doesn't support exceptions yet.
if (std.mem.startsWith(u8, cxxabi_src, "src/cxa_exception.cpp") or
std.mem.startsWith(u8, cxxabi_src, "src/cxa_personality.cpp"))
continue;
try cflags.append("-D_LIBCXXABI_HAS_NO_THREADS");
try cflags.append("-fno-exceptions");
}
// WASM targets are single threaded.
if (comp.bin_file.options.single_threaded) {
if (std.mem.startsWith(u8, cxxabi_src, "src/cxa_thread_atexit.cpp")) {
continue;
}
try cflags.append("-D_LIBCXXABI_HAS_NO_THREADS");
try cflags.append("-D_LIBCPP_HAS_NO_THREADS");
} else {
try cflags.append("-DHAVE___CXA_THREAD_ATEXIT_IMPL");
}

View File

@ -1,20 +1,21 @@
const std = @import("std");
const builtin = @import("builtin");
const Builder = std.build.Builder;
const CrossTarget = std.zig.CrossTarget;
// TODO integrate this with the std.build executor API
fn isRunnableTarget(t: CrossTarget) bool {
if (t.isNative()) return true;
return (t.getOsTag() == builtin.os.tag and
t.getCpuArch() == builtin.cpu.arch);
}
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const target = b.standardTargetOptions(.{});
const is_wine_enabled = b.option(bool, "enable-wine", "Use Wine to run cross compiled Windows tests") orelse false;
const is_qemu_enabled = b.option(bool, "enable-qemu", "Use QEMU to run cross compiled foreign architecture tests") orelse false;
const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false;
const is_darling_enabled = b.option(bool, "enable-darling", "[Experimental] Use Darling to run cross compiled macOS tests") orelse false;
const single_threaded = b.option(bool, "single-threaded", "Test single threaded mode") orelse false;
b.enable_wine = is_wine_enabled;
b.enable_qemu = is_qemu_enabled;
b.enable_wasmtime = is_wasmtime_enabled;
b.enable_darling = is_darling_enabled;
const test_step = b.step("test", "Test the program");
const exe_c = b.addExecutable("test_c", null);
@ -29,9 +30,16 @@ pub fn build(b: *Builder) void {
exe_cpp.addCSourceFile("test.cpp", &[0][]const u8{});
exe_cpp.setBuildMode(mode);
exe_cpp.setTarget(target);
exe_cpp.linkSystemLibrary("c++");
exe_cpp.linkLibCpp();
exe_cpp.single_threaded = single_threaded;
const os_tag = target.getOsTag();
// macos C++ exceptions could be compiled, but not being catched,
// additional support is required, possibly unwind + DWARF CFI
if (target.getCpuArch().isWasm() or os_tag == .macos) {
exe_cpp.defineCMacro("_LIBCPP_NO_EXCEPTIONS", null);
}
switch (target.getOsTag()) {
switch (os_tag) {
.windows => {
// https://github.com/ziglang/zig/issues/8531
exe_cpp.want_lto = false;
@ -44,13 +52,17 @@ pub fn build(b: *Builder) void {
else => {},
}
if (isRunnableTarget(target)) {
const run_c_cmd = exe_c.run();
const run_c_cmd = exe_c.run();
if (run_c_cmd.isRunnable()) {
test_step.dependOn(&run_c_cmd.step);
const run_cpp_cmd = exe_cpp.run();
test_step.dependOn(&run_cpp_cmd.step);
} else {
test_step.dependOn(&exe_c.step);
}
const run_cpp_cmd = exe_cpp.run();
if (run_cpp_cmd.isRunnable()) {
test_step.dependOn(&run_cpp_cmd.step);
} else {
test_step.dependOn(&exe_cpp.step);
}
}

View File

@ -1,25 +1,28 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int val;
typedef struct {
int val;
} STest;
int getVal(STest* data) { return data->val; }
int main (int argc, char *argv[])
{
STest* data = (STest*)malloc(sizeof(STest));
data->val = 123;
STest* data = (STest*)malloc(sizeof(STest));
data->val = 123;
assert(getVal(data) != 456);
int ok = (getVal(data) == 123);
assert(getVal(data) != 456);
int ok = (getVal(data) == 123);
if (argc>1) fprintf(stdout, "val=%d\n", data->val);
if (argc > 1) {
fprintf(stdout, "val=%d\n", data->val);
}
free(data);
free(data);
if (!ok) abort();
if (!ok) abort();
return 0;
return EXIT_SUCCESS;
}

View File

@ -1,15 +1,33 @@
#include <iostream>
#include <cassert>
#include <iostream>
#ifndef _LIBCPP_HAS_NO_THREADS
#include <future>
#endif
thread_local unsigned int tls_counter = 1;
// a non-optimized way of checking for prime numbers:
bool is_prime(int x) {
for (int i = 2; i <x ; ++i) {
if (x % i == 0) {
return false;
}
}
return true;
}
class CTest {
public:
CTest(int val) : m_val(val) {};
virtual ~CTest() {}
CTest(int val) : m_val(val) {
tls_counter++;
};
virtual ~CTest() {}
virtual int getVal() const { return m_val; }
virtual void printVal() { std::cout << "val=" << m_val << std::endl; }
virtual int getVal() const { return m_val; }
virtual void printVal() { std::cout << "val=" << m_val << std::endl; }
private:
int m_val;
int m_val;
};
@ -18,16 +36,30 @@ CTest global(runtime_val); // test if global initializers are called.
int main (int argc, char *argv[])
{
assert(global.getVal() == 456);
assert(global.getVal() == 456);
auto t = std::make_unique<CTest>(123);
assert(t->getVal() != 456);
assert(tls_counter == 2);
if (argc > 1) {
t->printVal();
}
bool ok = t->getVal() == 123;
auto* t = new CTest(123);
assert(t->getVal()!=456);
if (!ok) abort();
if (argc>1) t->printVal();
bool ok = t->getVal() == 123;
delete t;
#ifndef _LIBCPP_HAS_NO_THREADS
std::future<bool> fut = std::async(is_prime, 313);
bool ret = fut.get();
assert(ret);
#endif
if (!ok) abort();
#ifndef _LIBCPP_NO_EXCEPTIONS
try {
throw 20;
} catch (int e) {
assert(e == 20);
}
#endif
return 0;
return EXIT_SUCCESS;
}