diff options
| -rw-r--r-- | doc/langref.html.in | 19 | ||||
| -rw-r--r-- | src/all_types.hpp | 1 | ||||
| -rw-r--r-- | src/codegen.cpp | 4 | ||||
| -rw-r--r-- | src/main.cpp | 6 | ||||
| -rw-r--r-- | std/atomic/queue.zig | 42 | ||||
| -rw-r--r-- | std/atomic/stack.zig | 82 | ||||
| -rw-r--r-- | std/event/channel.zig | 3 | ||||
| -rw-r--r-- | std/event/future.zig | 3 | ||||
| -rw-r--r-- | std/event/group.zig | 3 | ||||
| -rw-r--r-- | std/event/lock.zig | 3 | ||||
| -rw-r--r-- | std/event/loop.zig | 22 | ||||
| -rw-r--r-- | std/event/net.zig | 3 | ||||
| -rw-r--r-- | std/event/rwlock.zig | 3 | ||||
| -rw-r--r-- | std/mutex.zig | 66 | ||||
| -rw-r--r-- | std/os/index.zig | 1 | ||||
| -rw-r--r-- | std/os/test.zig | 4 | ||||
| -rw-r--r-- | std/statically_initialized_mutex.zig | 21 | ||||
| -rw-r--r-- | test/tests.zig | 43 |
18 files changed, 246 insertions, 83 deletions
diff --git a/doc/langref.html.in b/doc/langref.html.in index e192dbbf76..144c8571c4 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6714,6 +6714,25 @@ pub fn build(b: *Builder) void { {#header_close#} {#see_also|Compile Variables|Zig Build System|Undefined Behavior#} {#header_close#} + + {#header_open|Single Threaded Builds#} + <p>Zig has a compile option <code>--single-threaded</code> which has the following effects: + <ul> + <li>{#link|@atomicLoad#} is emitted as a normal load.</li> + <li>{#link|@atomicRmw#} is emitted as a normal memory load, modify, store.</li> + <li>{#link|@fence#} becomes a no-op.</li> + <li>Variables which have Thread Local Storage instead become globals. TODO thread local variables + are not implemented yet.</li> + <li>The overhead of {#link|Coroutines#} becomes equivalent to function call overhead. + TODO: please note this will not be implemented until the upcoming Coroutine Rewrite</li> + <li>The {#syntax#}@import("builtin").single_threaded{#endsyntax#} becomes {#syntax#}true{#endsyntax#} + and therefore various userland APIs which read this variable become more efficient. + For example {#syntax#}std.Mutex{#endsyntax#} becomes + an empty data structure and all of its functions become no-ops.</li> + </ul> + </p> + {#header_close#} + {#header_open|Undefined Behavior#} <p> Zig has many instances of undefined behavior. If undefined behavior is diff --git a/src/all_types.hpp b/src/all_types.hpp index f6fe72891d..3fc6772b31 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1808,6 +1808,7 @@ struct CodeGen { bool is_static; bool strip_debug_symbols; bool is_test_build; + bool is_single_threaded; bool is_native_target; bool linker_rdynamic; bool no_rosegment_workaround; diff --git a/src/codegen.cpp b/src/codegen.cpp index e2576f5335..b73fda59d1 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -118,6 +118,7 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out g->string_literals_table.init(16); g->type_info_cache.init(32); g->is_test_build = false; + g->is_single_threaded = false; buf_resize(&g->global_asm, 0); for (size_t i = 0; i < array_length(symbols_that_llvm_depends_on); i += 1) { @@ -7377,6 +7378,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { buf_appendf(contents, "pub const endian = %s;\n", endian_str); } buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build)); + buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded)); buf_appendf(contents, "pub const os = Os.%s;\n", cur_os); buf_appendf(contents, "pub const arch = Arch.%s;\n", cur_arch); buf_appendf(contents, "pub const environ = Environ.%s;\n", cur_environ); @@ -7411,6 +7413,7 @@ static Error define_builtin_compile_vars(CodeGen *g) { cache_buf(&cache_hash, compiler_id); cache_int(&cache_hash, g->build_mode); cache_bool(&cache_hash, g->is_test_build); + cache_bool(&cache_hash, g->is_single_threaded); cache_int(&cache_hash, g->zig_target.arch.arch); cache_int(&cache_hash, g->zig_target.arch.sub_arch); cache_int(&cache_hash, g->zig_target.vendor); @@ -8329,6 +8332,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { cache_bool(ch, g->is_static); cache_bool(ch, g->strip_debug_symbols); cache_bool(ch, g->is_test_build); + cache_bool(ch, g->is_single_threaded); cache_bool(ch, g->is_native_target); cache_bool(ch, g->linker_rdynamic); cache_bool(ch, g->no_rosegment_workaround); diff --git a/src/main.cpp b/src/main.cpp index fd8e3db2fa..81f49089be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,6 +59,7 @@ static int print_full_usage(const char *arg0) { " --release-fast build with optimizations on and safety off\n" " --release-safe build with optimizations on and safety on\n" " --release-small build with size optimizations on and safety off\n" + " --single-threaded source may assume it is only used single-threaded\n" " --static output will be statically linked\n" " --strip exclude debug symbols\n" " --target-arch [name] specify target architecture\n" @@ -393,6 +394,7 @@ int main(int argc, char **argv) { bool no_rosegment_workaround = false; bool system_linker_hack = false; TargetSubsystem subsystem = TargetSubsystemAuto; + bool is_single_threaded = false; if (argc >= 2 && strcmp(argv[1], "build") == 0) { Buf zig_exe_path_buf = BUF_INIT; @@ -550,6 +552,8 @@ int main(int argc, char **argv) { disable_pic = true; } else if (strcmp(arg, "--system-linker-hack") == 0) { system_linker_hack = true; + } else if (strcmp(arg, "--single-threaded") == 0) { + is_single_threaded = true; } else if (strcmp(arg, "--test-cmd-bin") == 0) { test_exec_args.append(nullptr); } else if (arg[1] == 'L' && arg[2] != 0) { @@ -816,6 +820,7 @@ int main(int argc, char **argv) { switch (cmd) { case CmdBuiltin: { CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir()); + g->is_single_threaded = is_single_threaded; Buf *builtin_source = codegen_generate_builtin_source(g); if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) { fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout))); @@ -889,6 +894,7 @@ int main(int argc, char **argv) { codegen_set_out_name(g, buf_out_name); codegen_set_lib_version(g, ver_major, ver_minor, ver_patch); codegen_set_is_test(g, cmd == CmdTest); + g->is_single_threaded = is_single_threaded; codegen_set_linker_script(g, linker_script); if (each_lib_rpath) codegen_set_each_lib_rpath(g, each_lib_rpath); diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig index 1aab4c32de..6c61bcc048 100644 --- a/std/atomic/queue.zig +++ b/std/atomic/queue.zig @@ -170,20 +170,36 @@ test "std.atomic.Queue" { .get_count = 0, }; - var putters: [put_thread_count]*std.os.Thread = undefined; - for (putters) |*t| { - t.* = try std.os.spawnThread(&context, startPuts); - } - var getters: [put_thread_count]*std.os.Thread = undefined; - for (getters) |*t| { - t.* = try std.os.spawnThread(&context, startGets); - } + if (builtin.single_threaded) { + { + var i: usize = 0; + while (i < put_thread_count) : (i += 1) { + std.debug.assertOrPanic(startPuts(&context) == 0); + } + } + context.puts_done = 1; + { + var i: usize = 0; + while (i < put_thread_count) : (i += 1) { + std.debug.assertOrPanic(startGets(&context) == 0); + } + } + } else { + var putters: [put_thread_count]*std.os.Thread = undefined; + for (putters) |*t| { + t.* = try std.os.spawnThread(&context, startPuts); + } + var getters: [put_thread_count]*std.os.Thread = undefined; + for (getters) |*t| { + t.* = try std.os.spawnThread(&context, startGets); + } - for (putters) |t| - t.wait(); - _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); - for (getters) |t| - t.wait(); + for (putters) |t| + t.wait(); + _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); + for (getters) |t| + t.wait(); + } if (context.put_sum != context.get_sum) { std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum); diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig index b69a93733c..1e4981353b 100644 --- a/std/atomic/stack.zig +++ b/std/atomic/stack.zig @@ -4,10 +4,13 @@ const AtomicOrder = builtin.AtomicOrder; /// Many reader, many writer, non-allocating, thread-safe /// Uses a spinlock to protect push() and pop() +/// When building in single threaded mode, this is a simple linked list. pub fn Stack(comptime T: type) type { return struct { root: ?*Node, - lock: u8, + lock: @typeOf(lock_init), + + const lock_init = if (builtin.single_threaded) {} else u8(0); pub const Self = @This(); @@ -19,7 +22,7 @@ pub fn Stack(comptime T: type) type { pub fn init() Self { return Self{ .root = null, - .lock = 0, + .lock = lock_init, }; } @@ -31,20 +34,31 @@ pub fn Stack(comptime T: type) type { } pub fn push(self: *Self, node: *Node) void { - while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} - defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); - - node.next = self.root; - self.root = node; + if (builtin.single_threaded) { + node.next = self.root; + self.root = node; + } else { + while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} + defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); + + node.next = self.root; + self.root = node; + } } pub fn pop(self: *Self) ?*Node { - while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} - defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); - - const root = self.root orelse return null; - self.root = root.next; - return root; + if (builtin.single_threaded) { + const root = self.root orelse return null; + self.root = root.next; + return root; + } else { + while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} + defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); + + const root = self.root orelse return null; + self.root = root.next; + return root; + } } pub fn isEmpty(self: *Self) bool { @@ -90,20 +104,36 @@ test "std.atomic.stack" { .get_count = 0, }; - var putters: [put_thread_count]*std.os.Thread = undefined; - for (putters) |*t| { - t.* = try std.os.spawnThread(&context, startPuts); - } - var getters: [put_thread_count]*std.os.Thread = undefined; - for (getters) |*t| { - t.* = try std.os.spawnThread(&context, startGets); - } + if (builtin.single_threaded) { + { + var i: usize = 0; + while (i < put_thread_count) : (i += 1) { + std.debug.assertOrPanic(startPuts(&context) == 0); + } + } + context.puts_done = 1; + { + var i: usize = 0; + while (i < put_thread_count) : (i += 1) { + std.debug.assertOrPanic(startGets(&context) == 0); + } + } + } else { + var putters: [put_thread_count]*std.os.Thread = undefined; + for (putters) |*t| { + t.* = try std.os.spawnThread(&context, startPuts); + } + var getters: [put_thread_count]*std.os.Thread = undefined; + for (getters) |*t| { + t.* = try std.os.spawnThread(&context, startGets); + } - for (putters) |t| - t.wait(); - _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); - for (getters) |t| - t.wait(); + for (putters) |t| + t.wait(); + _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); + for (getters) |t| + t.wait(); + } if (context.put_sum != context.get_sum) { std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum); diff --git a/std/event/channel.zig b/std/event/channel.zig index 133ce1c69c..f04ad1604e 100644 --- a/std/event/channel.zig +++ b/std/event/channel.zig @@ -319,6 +319,9 @@ pub fn Channel(comptime T: type) type { } test "std.event.Channel" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + var da = std.heap.DirectAllocator.init(); defer da.deinit(); diff --git a/std/event/future.zig b/std/event/future.zig index d61768b198..55ed01046d 100644 --- a/std/event/future.zig +++ b/std/event/future.zig @@ -84,6 +84,9 @@ pub fn Future(comptime T: type) type { } test "std.event.Future" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + var da = std.heap.DirectAllocator.init(); defer da.deinit(); diff --git a/std/event/group.zig b/std/event/group.zig index 9f2687a5b3..0e9c2d9655 100644 --- a/std/event/group.zig +++ b/std/event/group.zig @@ -121,6 +121,9 @@ pub fn Group(comptime ReturnType: type) type { } test "std.event.Group" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + var da = std.heap.DirectAllocator.init(); defer da.deinit(); diff --git a/std/event/lock.zig b/std/event/lock.zig index 46e0d13468..01978e1909 100644 --- a/std/event/lock.zig +++ b/std/event/lock.zig @@ -122,6 +122,9 @@ pub const Lock = struct { }; test "std.event.Lock" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + var da = std.heap.DirectAllocator.init(); defer da.deinit(); diff --git a/std/event/loop.zig b/std/event/loop.zig index 43965e8293..d5228db604 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -97,6 +97,7 @@ pub const Loop = struct { /// TODO copy elision / named return values so that the threads referencing *Loop /// have the correct pointer value. pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void { + if (builtin.single_threaded) @compileError("initMultiThreaded unavailable when building in single-threaded mode"); const core_count = try os.cpuCount(allocator); return self.initInternal(allocator, core_count); } @@ -201,6 +202,11 @@ pub const Loop = struct { self.os_data.fs_thread.wait(); } + if (builtin.single_threaded) { + assert(extra_thread_count == 0); + return; + } + var extra_thread_index: usize = 0; errdefer { // writing 8 bytes to an eventfd cannot fail @@ -301,6 +307,11 @@ pub const Loop = struct { self.os_data.fs_thread.wait(); } + if (builtin.single_threaded) { + assert(extra_thread_count == 0); + return; + } + var extra_thread_index: usize = 0; errdefer { _ = os.bsdKEvent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable; @@ -338,6 +349,11 @@ pub const Loop = struct { self.available_eventfd_resume_nodes.push(eventfd_node); } + if (builtin.single_threaded) { + assert(extra_thread_count == 0); + return; + } + var extra_thread_index: usize = 0; errdefer { var i: usize = 0; @@ -845,6 +861,9 @@ pub const Loop = struct { }; test "std.event.Loop - basic" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + var da = std.heap.DirectAllocator.init(); defer da.deinit(); @@ -858,6 +877,9 @@ test "std.event.Loop - basic" { } test "std.event.Loop - call" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + var da = std.heap.DirectAllocator.init(); defer da.deinit(); diff --git a/std/event/net.zig b/std/event/net.zig index 6838704084..9dac6aa566 100644 --- a/std/event/net.zig +++ b/std/event/net.zig @@ -269,6 +269,9 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !os.File { } test "listen on a port, send bytes, receive bytes" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + if (builtin.os != builtin.Os.linux) { // TODO build abstractions for other operating systems return error.SkipZigTest; diff --git a/std/event/rwlock.zig b/std/event/rwlock.zig index 5d48ea893e..f272ac71ea 100644 --- a/std/event/rwlock.zig +++ b/std/event/rwlock.zig @@ -211,6 +211,9 @@ pub const RwLock = struct { }; test "std.event.RwLock" { + // https://github.com/ziglang/zig/issues/1908 + if (builtin.single_threaded) return error.SkipZigTest; + var da = std.heap.DirectAllocator.init(); defer da.deinit(); diff --git a/std/mutex.zig b/std/mutex.zig index 723581cbef..54173fa38a 100644 --- a/std/mutex.zig +++ b/std/mutex.zig @@ -14,7 +14,36 @@ const windows = std.os.windows; /// If you need static initialization, use std.StaticallyInitializedMutex. /// The Linux implementation is based on mutex3 from /// https://www.akkadia.org/drepper/futex.pdf -pub const Mutex = switch(builtin.os) { +/// When an application is built in single threaded release mode, all the functions are +/// no-ops. In single threaded debug mode, there is deadlock detection. +pub const Mutex = if (builtin.single_threaded) + struct { + lock: @typeOf(lock_init), + + const lock_init = if (std.debug.runtime_safety) false else {}; + + pub const Held = struct { + mutex: *Mutex, + + pub fn release(self: Held) void { + if (std.debug.runtime_safety) { + self.mutex.lock = false; + } + } + }; + pub fn init() Mutex { + return Mutex{ .lock = lock_init }; + } + pub fn deinit(self: *Mutex) void {} + + pub fn acquire(self: *Mutex) Held { + if (std.debug.runtime_safety and self.lock) { + @panic("deadlock detected"); + } + return Held{ .mutex = self }; + } + } +else switch (builtin.os) { builtin.Os.linux => struct { /// 0: unlocked /// 1: locked, no waiters @@ -39,9 +68,7 @@ pub const Mutex = switch(builtin.os) { }; pub fn init() Mutex { - return Mutex { - .lock = 0, - }; + return Mutex{ .lock = 0 }; } pub fn deinit(self: *Mutex) void {} @@ -60,7 +87,7 @@ pub const Mutex = switch(builtin.os) { } c = @atomicRmw(i32, &self.lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire); } - return Held { .mutex = self }; + return Held{ .mutex = self }; } }, // TODO once https://github.com/ziglang/zig/issues/287 (copy elision) is solved, we can make a @@ -78,21 +105,19 @@ pub const Mutex = switch(builtin.os) { mutex: *Mutex, pub fn release(self: Held) void { - SpinLock.Held.release(SpinLock.Held { .spinlock = &self.mutex.lock }); + SpinLock.Held.release(SpinLock.Held{ .spinlock = &self.mutex.lock }); } }; pub fn init() Mutex { - return Mutex { - .lock = SpinLock.init(), - }; + return Mutex{ .lock = SpinLock.init() }; } pub fn deinit(self: *Mutex) void {} pub fn acquire(self: *Mutex) Held { _ = self.lock.acquire(); - return Held { .mutex = self }; + return Held{ .mutex = self }; } }, }; @@ -122,15 +147,20 @@ test "std.Mutex" { .data = 0, }; - const thread_count = 10; - var threads: [thread_count]*std.os.Thread = undefined; - for (threads) |*t| { - t.* = try std.os.spawnThread(&context, worker); - } - for (threads) |t| - t.wait(); + if (builtin.single_threaded) { + worker(&context); + std.debug.assertOrPanic(context.data == TestContext.incr_count); + } else { + const thread_count = 10; + var threads: [thread_count]*std.os.Thread = undefined; + for (threads) |*t| { + t.* = try std.os.spawnThread(&context, worker); + } + for (threads) |t| + t.wait(); - std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count); + std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count); + } } fn worker(ctx: *TestContext) void { diff --git a/std/os/index.zig b/std/os/index.zig index 78d543583d..75abe3bbde 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -3013,6 +3013,7 @@ pub const SpawnThreadError = error{ /// where T is u8, noreturn, void, or !void /// caller must call wait on the returned thread pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread { + if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); // TODO compile-time call graph analysis to determine stack upper bound // https://github.com/ziglang/zig/issues/157 const default_stack_size = 8 * 1024 * 1024; diff --git a/std/os/test.zig b/std/os/test.zig index 5142920687..f14cf47786 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -40,6 +40,8 @@ fn testThreadIdFn(thread_id: *os.Thread.Id) void { } test "std.os.Thread.getCurrentId" { + if (builtin.single_threaded) return error.SkipZigTest; + var thread_current_id: os.Thread.Id = undefined; const thread = try os.spawnThread(&thread_current_id, testThreadIdFn); const thread_id = thread.handle(); @@ -53,6 +55,8 @@ test "std.os.Thread.getCurrentId" { } test "spawn threads" { + if (builtin.single_threaded) return error.SkipZigTest; + var shared_ctx: i32 = 1; const thread1 = try std.os.spawnThread({}, start1); diff --git a/std/statically_initialized_mutex.zig b/std/statically_initialized_mutex.zig index dd875eeaf9..37582d49c1 100644 --- a/std/statically_initialized_mutex.zig +++ b/std/statically_initialized_mutex.zig @@ -93,13 +93,18 @@ test "std.StaticallyInitializedMutex" { .data = 0, }; - const thread_count = 10; - var threads: [thread_count]*std.os.Thread = undefined; - for (threads) |*t| { - t.* = try std.os.spawnThread(&context, TestContext.worker); - } - for (threads) |t| - t.wait(); + if (builtin.single_threaded) { + TestContext.worker(&context); + std.debug.assertOrPanic(context.data == TestContext.incr_count); + } else { + const thread_count = 10; + var threads: [thread_count]*std.os.Thread = undefined; + for (threads) |*t| { + t.* = try std.os.spawnThread(&context, TestContext.worker); + } + for (threads) |t| + t.wait(); - std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count); + std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count); + } } diff --git a/test/tests.zig b/test/tests.zig index 1ca06b4b34..73d4644d18 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -163,25 +163,32 @@ pub fn addPkgTests(b: *build.Builder, test_filter: ?[]const u8, root_src: []cons for (test_targets) |test_target| { const is_native = (test_target.os == builtin.os and test_target.arch == builtin.arch); for (modes) |mode| { - for ([]bool{ - false, - true, - }) |link_libc| { - if (link_libc and !is_native) { - // don't assume we have a cross-compiling libc set up - continue; - } - const these_tests = b.addTest(root_src); - these_tests.setNamePrefix(b.fmt("{}-{}-{}-{}-{} ", name, @tagName(test_target.os), @tagName(test_target.arch), @tagName(mode), if (link_libc) "c" else "bare")); - these_tests.setFilter(test_filter); - these_tests.setBuildMode(mode); - if (!is_native) { - these_tests.setTarget(test_target.arch, test_target.os, test_target.environ); - } - if (link_libc) { - these_tests.linkSystemLibrary("c"); + for ([]bool{ false, true }) |link_libc| { + for ([]bool{ false, true }) |single_threaded| { + if (link_libc and !is_native) { + // don't assume we have a cross-compiling libc set up + continue; + } + const these_tests = b.addTest(root_src); + these_tests.setNamePrefix(b.fmt( + "{}-{}-{}-{}-{}-{} ", + name, + @tagName(test_target.os), + @tagName(test_target.arch), + @tagName(mode), + if (link_libc) "c" else "bare", + if (single_threaded) "single" else "multi", + )); + these_tests.setFilter(test_filter); + these_tests.setBuildMode(mode); + if (!is_native) { + these_tests.setTarget(test_target.arch, test_target.os, test_target.environ); + } + if (link_libc) { + these_tests.linkSystemLibrary("c"); + } + step.dependOn(&these_tests.step); } - step.dependOn(&these_tests.step); } } } |
