Commit 3997828a61

Alexander Slesarev <alex.slesarev@gmail.com>
2021-11-01 05:36:30
Added _LIBCPP_HAS_NO_THREADS for single_threaded binaries linked with libcxx.
Fixed single-threaded mode for Windows.
1 parent 67c4b16
Changed files (6)
lib
std
src
test
standalone
lib/std/build/RunStep.zig
@@ -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);
             },
src/Compilation.zig
@@ -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) {
src/libcxx.zig
@@ -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");
         }
test/standalone/c_compiler/build.zig
@@ -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);
     }
 }
test/standalone/c_compiler/test.c
@@ -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;
 }
test/standalone/c_compiler/test.cpp
@@ -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;
 }