authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-01 17:49:29-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-01 18:05:54-05:00
log9b8e23934bc87f1fd6a42cdfdd551212994b6e58
tree0ac63357b6504f7bf44e66002b0ccca49dc64b45
parent8bedb10939b511e39787b2693249d2d3e8102854
signaturelock-open Commit is signed but in an unrecognized format.

introduce --single-threaded build option

closes #1764 This adds another boolean to the test matrix; hopefully it does not inflate the time too much. std.event.Loop does not work with this option yet. See #1908

18 files changed, 246 insertions(+), 83 deletions(-)

doc/langref.html.in+19
...@@ -6714,6 +6714,25 @@ pub fn build(b: *Builder) void {...@@ -6714,6 +6714,25 @@ pub fn build(b: *Builder) void {
6714 {#header_close#}6714 {#header_close#}
6715 {#see_also|Compile Variables|Zig Build System|Undefined Behavior#}6715 {#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
6716 {#header_close#}6716 {#header_close#}
6717
6718 {#header_open|Single Threaded Builds#}
6719 <p>Zig has a compile option <code>--single-threaded</code> which has the following effects:
6720 <ul>
6721 <li>{#link|@atomicLoad#} is emitted as a normal load.</li>
6722 <li>{#link|@atomicRmw#} is emitted as a normal memory load, modify, store.</li>
6723 <li>{#link|@fence#} becomes a no-op.</li>
6724 <li>Variables which have Thread Local Storage instead become globals. TODO thread local variables
6725 are not implemented yet.</li>
6726 <li>The overhead of {#link|Coroutines#} becomes equivalent to function call overhead.
6727 TODO: please note this will not be implemented until the upcoming Coroutine Rewrite</li>
6728 <li>The {#syntax#}@import("builtin").single_threaded{#endsyntax#} becomes {#syntax#}true{#endsyntax#}
6729 and therefore various userland APIs which read this variable become more efficient.
6730 For example {#syntax#}std.Mutex{#endsyntax#} becomes
6731 an empty data structure and all of its functions become no-ops.</li>
6732 </ul>
6733 </p>
6734 {#header_close#}
6735
6717 {#header_open|Undefined Behavior#}6736 {#header_open|Undefined Behavior#}
6718 <p>6737 <p>
6719 Zig has many instances of undefined behavior. If undefined behavior is6738 Zig has many instances of undefined behavior. If undefined behavior is
src/all_types.hpp+1
...@@ -1808,6 +1808,7 @@ struct CodeGen {...@@ -1808,6 +1808,7 @@ struct CodeGen {
1808 bool is_static;1808 bool is_static;
1809 bool strip_debug_symbols;1809 bool strip_debug_symbols;
1810 bool is_test_build;1810 bool is_test_build;
1811 bool is_single_threaded;
1811 bool is_native_target;1812 bool is_native_target;
1812 bool linker_rdynamic;1813 bool linker_rdynamic;
1813 bool no_rosegment_workaround;1814 bool no_rosegment_workaround;
src/codegen.cpp+4
...@@ -118,6 +118,7 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out...@@ -118,6 +118,7 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out
118 g->string_literals_table.init(16);118 g->string_literals_table.init(16);
119 g->type_info_cache.init(32);119 g->type_info_cache.init(32);
120 g->is_test_build = false;120 g->is_test_build = false;
121 g->is_single_threaded = false;
121 buf_resize(&g->global_asm, 0);122 buf_resize(&g->global_asm, 0);
122123
123 for (size_t i = 0; i < array_length(symbols_that_llvm_depends_on); i += 1) {124 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) {...@@ -7377,6 +7378,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
7377 buf_appendf(contents, "pub const endian = %s;\n", endian_str);7378 buf_appendf(contents, "pub const endian = %s;\n", endian_str);
7378 }7379 }
7379 buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build));7380 buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build));
7381 buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded));
7380 buf_appendf(contents, "pub const os = Os.%s;\n", cur_os);7382 buf_appendf(contents, "pub const os = Os.%s;\n", cur_os);
7381 buf_appendf(contents, "pub const arch = Arch.%s;\n", cur_arch);7383 buf_appendf(contents, "pub const arch = Arch.%s;\n", cur_arch);
7382 buf_appendf(contents, "pub const environ = Environ.%s;\n", cur_environ);7384 buf_appendf(contents, "pub const environ = Environ.%s;\n", cur_environ);
...@@ -7411,6 +7413,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {...@@ -7411,6 +7413,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
7411 cache_buf(&cache_hash, compiler_id);7413 cache_buf(&cache_hash, compiler_id);
7412 cache_int(&cache_hash, g->build_mode);7414 cache_int(&cache_hash, g->build_mode);
7413 cache_bool(&cache_hash, g->is_test_build);7415 cache_bool(&cache_hash, g->is_test_build);
7416 cache_bool(&cache_hash, g->is_single_threaded);
7414 cache_int(&cache_hash, g->zig_target.arch.arch);7417 cache_int(&cache_hash, g->zig_target.arch.arch);
7415 cache_int(&cache_hash, g->zig_target.arch.sub_arch);7418 cache_int(&cache_hash, g->zig_target.arch.sub_arch);
7416 cache_int(&cache_hash, g->zig_target.vendor);7419 cache_int(&cache_hash, g->zig_target.vendor);
...@@ -8329,6 +8332,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {...@@ -8329,6 +8332,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
8329 cache_bool(ch, g->is_static);8332 cache_bool(ch, g->is_static);
8330 cache_bool(ch, g->strip_debug_symbols);8333 cache_bool(ch, g->strip_debug_symbols);
8331 cache_bool(ch, g->is_test_build);8334 cache_bool(ch, g->is_test_build);
8335 cache_bool(ch, g->is_single_threaded);
8332 cache_bool(ch, g->is_native_target);8336 cache_bool(ch, g->is_native_target);
8333 cache_bool(ch, g->linker_rdynamic);8337 cache_bool(ch, g->linker_rdynamic);
8334 cache_bool(ch, g->no_rosegment_workaround);8338 cache_bool(ch, g->no_rosegment_workaround);
src/main.cpp+6
...@@ -59,6 +59,7 @@ static int print_full_usage(const char *arg0) {...@@ -59,6 +59,7 @@ static int print_full_usage(const char *arg0) {
59 " --release-fast build with optimizations on and safety off\n"59 " --release-fast build with optimizations on and safety off\n"
60 " --release-safe build with optimizations on and safety on\n"60 " --release-safe build with optimizations on and safety on\n"
61 " --release-small build with size optimizations on and safety off\n"61 " --release-small build with size optimizations on and safety off\n"
62 " --single-threaded source may assume it is only used single-threaded\n"
62 " --static output will be statically linked\n"63 " --static output will be statically linked\n"
63 " --strip exclude debug symbols\n"64 " --strip exclude debug symbols\n"
64 " --target-arch [name] specify target architecture\n"65 " --target-arch [name] specify target architecture\n"
...@@ -393,6 +394,7 @@ int main(int argc, char **argv) {...@@ -393,6 +394,7 @@ int main(int argc, char **argv) {
393 bool no_rosegment_workaround = false;394 bool no_rosegment_workaround = false;
394 bool system_linker_hack = false;395 bool system_linker_hack = false;
395 TargetSubsystem subsystem = TargetSubsystemAuto;396 TargetSubsystem subsystem = TargetSubsystemAuto;
397 bool is_single_threaded = false;
396398
397 if (argc >= 2 && strcmp(argv[1], "build") == 0) {399 if (argc >= 2 && strcmp(argv[1], "build") == 0) {
398 Buf zig_exe_path_buf = BUF_INIT;400 Buf zig_exe_path_buf = BUF_INIT;
...@@ -550,6 +552,8 @@ int main(int argc, char **argv) {...@@ -550,6 +552,8 @@ int main(int argc, char **argv) {
550 disable_pic = true;552 disable_pic = true;
551 } else if (strcmp(arg, "--system-linker-hack") == 0) {553 } else if (strcmp(arg, "--system-linker-hack") == 0) {
552 system_linker_hack = true;554 system_linker_hack = true;
555 } else if (strcmp(arg, "--single-threaded") == 0) {
556 is_single_threaded = true;
553 } else if (strcmp(arg, "--test-cmd-bin") == 0) {557 } else if (strcmp(arg, "--test-cmd-bin") == 0) {
554 test_exec_args.append(nullptr);558 test_exec_args.append(nullptr);
555 } else if (arg[1] == 'L' && arg[2] != 0) {559 } else if (arg[1] == 'L' && arg[2] != 0) {
...@@ -816,6 +820,7 @@ int main(int argc, char **argv) {...@@ -816,6 +820,7 @@ int main(int argc, char **argv) {
816 switch (cmd) {820 switch (cmd) {
817 case CmdBuiltin: {821 case CmdBuiltin: {
818 CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir());822 CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir());
823 g->is_single_threaded = is_single_threaded;
819 Buf *builtin_source = codegen_generate_builtin_source(g);824 Buf *builtin_source = codegen_generate_builtin_source(g);
820 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {825 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {
821 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));826 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));
...@@ -889,6 +894,7 @@ int main(int argc, char **argv) {...@@ -889,6 +894,7 @@ int main(int argc, char **argv) {
889 codegen_set_out_name(g, buf_out_name);894 codegen_set_out_name(g, buf_out_name);
890 codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);895 codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
891 codegen_set_is_test(g, cmd == CmdTest);896 codegen_set_is_test(g, cmd == CmdTest);
897 g->is_single_threaded = is_single_threaded;
892 codegen_set_linker_script(g, linker_script);898 codegen_set_linker_script(g, linker_script);
893 if (each_lib_rpath)899 if (each_lib_rpath)
894 codegen_set_each_lib_rpath(g, each_lib_rpath);900 codegen_set_each_lib_rpath(g, each_lib_rpath);
std/atomic/queue.zig+29-13
...@@ -170,20 +170,36 @@ test "std.atomic.Queue" {...@@ -170,20 +170,36 @@ test "std.atomic.Queue" {
170 .get_count = 0,170 .get_count = 0,
171 };171 };
172172
173 var putters: [put_thread_count]*std.os.Thread = undefined;173 if (builtin.single_threaded) {
174 for (putters) |*t| {174 {
175 t.* = try std.os.spawnThread(&context, startPuts);175 var i: usize = 0;
176 }176 while (i < put_thread_count) : (i += 1) {
177 var getters: [put_thread_count]*std.os.Thread = undefined;177 std.debug.assertOrPanic(startPuts(&context) == 0);
178 for (getters) |*t| {178 }
179 t.* = try std.os.spawnThread(&context, startGets);179 }
180 }180 context.puts_done = 1;
181 {
182 var i: usize = 0;
183 while (i < put_thread_count) : (i += 1) {
184 std.debug.assertOrPanic(startGets(&context) == 0);
185 }
186 }
187 } else {
188 var putters: [put_thread_count]*std.os.Thread = undefined;
189 for (putters) |*t| {
190 t.* = try std.os.spawnThread(&context, startPuts);
191 }
192 var getters: [put_thread_count]*std.os.Thread = undefined;
193 for (getters) |*t| {
194 t.* = try std.os.spawnThread(&context, startGets);
195 }
181196
182 for (putters) |t|197 for (putters) |t|
183 t.wait();198 t.wait();
184 _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);199 _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
185 for (getters) |t|200 for (getters) |t|
186 t.wait();201 t.wait();
202 }
187203
188 if (context.put_sum != context.get_sum) {204 if (context.put_sum != context.get_sum) {
189 std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum);205 std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum);
std/atomic/stack.zig+56-26
...@@ -4,10 +4,13 @@ const AtomicOrder = builtin.AtomicOrder;...@@ -4,10 +4,13 @@ const AtomicOrder = builtin.AtomicOrder;
44
5/// Many reader, many writer, non-allocating, thread-safe5/// Many reader, many writer, non-allocating, thread-safe
6/// Uses a spinlock to protect push() and pop()6/// Uses a spinlock to protect push() and pop()
7/// When building in single threaded mode, this is a simple linked list.
7pub fn Stack(comptime T: type) type {8pub fn Stack(comptime T: type) type {
8 return struct {9 return struct {
9 root: ?*Node,10 root: ?*Node,
10 lock: u8,11 lock: @typeOf(lock_init),
12
13 const lock_init = if (builtin.single_threaded) {} else u8(0);
1114
12 pub const Self = @This();15 pub const Self = @This();
1316
...@@ -19,7 +22,7 @@ pub fn Stack(comptime T: type) type {...@@ -19,7 +22,7 @@ pub fn Stack(comptime T: type) type {
19 pub fn init() Self {22 pub fn init() Self {
20 return Self{23 return Self{
21 .root = null,24 .root = null,
22 .lock = 0,25 .lock = lock_init,
23 };26 };
24 }27 }
2528
...@@ -31,20 +34,31 @@ pub fn Stack(comptime T: type) type {...@@ -31,20 +34,31 @@ pub fn Stack(comptime T: type) type {
31 }34 }
3235
33 pub fn push(self: *Self, node: *Node) void {36 pub fn push(self: *Self, node: *Node) void {
34 while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}37 if (builtin.single_threaded) {
35 defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);38 node.next = self.root;
3639 self.root = node;
37 node.next = self.root;40 } else {
38 self.root = node;41 while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
42 defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
43
44 node.next = self.root;
45 self.root = node;
46 }
39 }47 }
4048
41 pub fn pop(self: *Self) ?*Node {49 pub fn pop(self: *Self) ?*Node {
42 while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}50 if (builtin.single_threaded) {
43 defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);51 const root = self.root orelse return null;
4452 self.root = root.next;
45 const root = self.root orelse return null;53 return root;
46 self.root = root.next;54 } else {
47 return root;55 while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
56 defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
57
58 const root = self.root orelse return null;
59 self.root = root.next;
60 return root;
61 }
48 }62 }
4963
50 pub fn isEmpty(self: *Self) bool {64 pub fn isEmpty(self: *Self) bool {
...@@ -90,20 +104,36 @@ test "std.atomic.stack" {...@@ -90,20 +104,36 @@ test "std.atomic.stack" {
90 .get_count = 0,104 .get_count = 0,
91 };105 };
92106
93 var putters: [put_thread_count]*std.os.Thread = undefined;107 if (builtin.single_threaded) {
94 for (putters) |*t| {108 {
95 t.* = try std.os.spawnThread(&context, startPuts);109 var i: usize = 0;
96 }110 while (i < put_thread_count) : (i += 1) {
97 var getters: [put_thread_count]*std.os.Thread = undefined;111 std.debug.assertOrPanic(startPuts(&context) == 0);
98 for (getters) |*t| {112 }
99 t.* = try std.os.spawnThread(&context, startGets);113 }
100 }114 context.puts_done = 1;
115 {
116 var i: usize = 0;
117 while (i < put_thread_count) : (i += 1) {
118 std.debug.assertOrPanic(startGets(&context) == 0);
119 }
120 }
121 } else {
122 var putters: [put_thread_count]*std.os.Thread = undefined;
123 for (putters) |*t| {
124 t.* = try std.os.spawnThread(&context, startPuts);
125 }
126 var getters: [put_thread_count]*std.os.Thread = undefined;
127 for (getters) |*t| {
128 t.* = try std.os.spawnThread(&context, startGets);
129 }
101130
102 for (putters) |t|131 for (putters) |t|
103 t.wait();132 t.wait();
104 _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);133 _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
105 for (getters) |t|134 for (getters) |t|
106 t.wait();135 t.wait();
136 }
107137
108 if (context.put_sum != context.get_sum) {138 if (context.put_sum != context.get_sum) {
109 std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum);139 std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum);
std/event/channel.zig+3
...@@ -319,6 +319,9 @@ pub fn Channel(comptime T: type) type {...@@ -319,6 +319,9 @@ pub fn Channel(comptime T: type) type {
319}319}
320320
321test "std.event.Channel" {321test "std.event.Channel" {
322 // https://github.com/ziglang/zig/issues/1908
323 if (builtin.single_threaded) return error.SkipZigTest;
324
322 var da = std.heap.DirectAllocator.init();325 var da = std.heap.DirectAllocator.init();
323 defer da.deinit();326 defer da.deinit();
324327
std/event/future.zig+3
...@@ -84,6 +84,9 @@ pub fn Future(comptime T: type) type {...@@ -84,6 +84,9 @@ pub fn Future(comptime T: type) type {
84}84}
8585
86test "std.event.Future" {86test "std.event.Future" {
87 // https://github.com/ziglang/zig/issues/1908
88 if (builtin.single_threaded) return error.SkipZigTest;
89
87 var da = std.heap.DirectAllocator.init();90 var da = std.heap.DirectAllocator.init();
88 defer da.deinit();91 defer da.deinit();
8992
std/event/group.zig+3
...@@ -121,6 +121,9 @@ pub fn Group(comptime ReturnType: type) type {...@@ -121,6 +121,9 @@ pub fn Group(comptime ReturnType: type) type {
121}121}
122122
123test "std.event.Group" {123test "std.event.Group" {
124 // https://github.com/ziglang/zig/issues/1908
125 if (builtin.single_threaded) return error.SkipZigTest;
126
124 var da = std.heap.DirectAllocator.init();127 var da = std.heap.DirectAllocator.init();
125 defer da.deinit();128 defer da.deinit();
126129
std/event/lock.zig+3
...@@ -122,6 +122,9 @@ pub const Lock = struct {...@@ -122,6 +122,9 @@ pub const Lock = struct {
122};122};
123123
124test "std.event.Lock" {124test "std.event.Lock" {
125 // https://github.com/ziglang/zig/issues/1908
126 if (builtin.single_threaded) return error.SkipZigTest;
127
125 var da = std.heap.DirectAllocator.init();128 var da = std.heap.DirectAllocator.init();
126 defer da.deinit();129 defer da.deinit();
127130
std/event/loop.zig+22
...@@ -97,6 +97,7 @@ pub const Loop = struct {...@@ -97,6 +97,7 @@ pub const Loop = struct {
97 /// TODO copy elision / named return values so that the threads referencing *Loop97 /// TODO copy elision / named return values so that the threads referencing *Loop
98 /// have the correct pointer value.98 /// have the correct pointer value.
99 pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {99 pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {
100 if (builtin.single_threaded) @compileError("initMultiThreaded unavailable when building in single-threaded mode");
100 const core_count = try os.cpuCount(allocator);101 const core_count = try os.cpuCount(allocator);
101 return self.initInternal(allocator, core_count);102 return self.initInternal(allocator, core_count);
102 }103 }
...@@ -201,6 +202,11 @@ pub const Loop = struct {...@@ -201,6 +202,11 @@ pub const Loop = struct {
201 self.os_data.fs_thread.wait();202 self.os_data.fs_thread.wait();
202 }203 }
203204
205 if (builtin.single_threaded) {
206 assert(extra_thread_count == 0);
207 return;
208 }
209
204 var extra_thread_index: usize = 0;210 var extra_thread_index: usize = 0;
205 errdefer {211 errdefer {
206 // writing 8 bytes to an eventfd cannot fail212 // writing 8 bytes to an eventfd cannot fail
...@@ -301,6 +307,11 @@ pub const Loop = struct {...@@ -301,6 +307,11 @@ pub const Loop = struct {
301 self.os_data.fs_thread.wait();307 self.os_data.fs_thread.wait();
302 }308 }
303309
310 if (builtin.single_threaded) {
311 assert(extra_thread_count == 0);
312 return;
313 }
314
304 var extra_thread_index: usize = 0;315 var extra_thread_index: usize = 0;
305 errdefer {316 errdefer {
306 _ = os.bsdKEvent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;317 _ = os.bsdKEvent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;
...@@ -338,6 +349,11 @@ pub const Loop = struct {...@@ -338,6 +349,11 @@ pub const Loop = struct {
338 self.available_eventfd_resume_nodes.push(eventfd_node);349 self.available_eventfd_resume_nodes.push(eventfd_node);
339 }350 }
340351
352 if (builtin.single_threaded) {
353 assert(extra_thread_count == 0);
354 return;
355 }
356
341 var extra_thread_index: usize = 0;357 var extra_thread_index: usize = 0;
342 errdefer {358 errdefer {
343 var i: usize = 0;359 var i: usize = 0;
...@@ -845,6 +861,9 @@ pub const Loop = struct {...@@ -845,6 +861,9 @@ pub const Loop = struct {
845};861};
846862
847test "std.event.Loop - basic" {863test "std.event.Loop - basic" {
864 // https://github.com/ziglang/zig/issues/1908
865 if (builtin.single_threaded) return error.SkipZigTest;
866
848 var da = std.heap.DirectAllocator.init();867 var da = std.heap.DirectAllocator.init();
849 defer da.deinit();868 defer da.deinit();
850869
...@@ -858,6 +877,9 @@ test "std.event.Loop - basic" {...@@ -858,6 +877,9 @@ test "std.event.Loop - basic" {
858}877}
859878
860test "std.event.Loop - call" {879test "std.event.Loop - call" {
880 // https://github.com/ziglang/zig/issues/1908
881 if (builtin.single_threaded) return error.SkipZigTest;
882
861 var da = std.heap.DirectAllocator.init();883 var da = std.heap.DirectAllocator.init();
862 defer da.deinit();884 defer da.deinit();
863885
std/event/net.zig+3
...@@ -269,6 +269,9 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !os.File {...@@ -269,6 +269,9 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !os.File {
269}269}
270270
271test "listen on a port, send bytes, receive bytes" {271test "listen on a port, send bytes, receive bytes" {
272 // https://github.com/ziglang/zig/issues/1908
273 if (builtin.single_threaded) return error.SkipZigTest;
274
272 if (builtin.os != builtin.Os.linux) {275 if (builtin.os != builtin.Os.linux) {
273 // TODO build abstractions for other operating systems276 // TODO build abstractions for other operating systems
274 return error.SkipZigTest;277 return error.SkipZigTest;
std/event/rwlock.zig+3
...@@ -211,6 +211,9 @@ pub const RwLock = struct {...@@ -211,6 +211,9 @@ pub const RwLock = struct {
211};211};
212212
213test "std.event.RwLock" {213test "std.event.RwLock" {
214 // https://github.com/ziglang/zig/issues/1908
215 if (builtin.single_threaded) return error.SkipZigTest;
216
214 var da = std.heap.DirectAllocator.init();217 var da = std.heap.DirectAllocator.init();
215 defer da.deinit();218 defer da.deinit();
216219
std/mutex.zig+48-18
...@@ -14,7 +14,36 @@ const windows = std.os.windows;...@@ -14,7 +14,36 @@ const windows = std.os.windows;
14/// If you need static initialization, use std.StaticallyInitializedMutex.14/// If you need static initialization, use std.StaticallyInitializedMutex.
15/// The Linux implementation is based on mutex3 from15/// The Linux implementation is based on mutex3 from
16/// https://www.akkadia.org/drepper/futex.pdf16/// https://www.akkadia.org/drepper/futex.pdf
17pub const Mutex = switch(builtin.os) {17/// When an application is built in single threaded release mode, all the functions are
18/// no-ops. In single threaded debug mode, there is deadlock detection.
19pub const Mutex = if (builtin.single_threaded)
20 struct {
21 lock: @typeOf(lock_init),
22
23 const lock_init = if (std.debug.runtime_safety) false else {};
24
25 pub const Held = struct {
26 mutex: *Mutex,
27
28 pub fn release(self: Held) void {
29 if (std.debug.runtime_safety) {
30 self.mutex.lock = false;
31 }
32 }
33 };
34 pub fn init() Mutex {
35 return Mutex{ .lock = lock_init };
36 }
37 pub fn deinit(self: *Mutex) void {}
38
39 pub fn acquire(self: *Mutex) Held {
40 if (std.debug.runtime_safety and self.lock) {
41 @panic("deadlock detected");
42 }
43 return Held{ .mutex = self };
44 }
45 }
46else switch (builtin.os) {
18 builtin.Os.linux => struct {47 builtin.Os.linux => struct {
19 /// 0: unlocked48 /// 0: unlocked
20 /// 1: locked, no waiters49 /// 1: locked, no waiters
...@@ -39,9 +68,7 @@ pub const Mutex = switch(builtin.os) {...@@ -39,9 +68,7 @@ pub const Mutex = switch(builtin.os) {
39 };68 };
4069
41 pub fn init() Mutex {70 pub fn init() Mutex {
42 return Mutex {71 return Mutex{ .lock = 0 };
43 .lock = 0,
44 };
45 }72 }
4673
47 pub fn deinit(self: *Mutex) void {}74 pub fn deinit(self: *Mutex) void {}
...@@ -60,7 +87,7 @@ pub const Mutex = switch(builtin.os) {...@@ -60,7 +87,7 @@ pub const Mutex = switch(builtin.os) {
60 }87 }
61 c = @atomicRmw(i32, &self.lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);88 c = @atomicRmw(i32, &self.lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);
62 }89 }
63 return Held { .mutex = self };90 return Held{ .mutex = self };
64 }91 }
65 },92 },
66 // TODO once https://github.com/ziglang/zig/issues/287 (copy elision) is solved, we can make a93 // 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) {...@@ -78,21 +105,19 @@ pub const Mutex = switch(builtin.os) {
78 mutex: *Mutex,105 mutex: *Mutex,
79106
80 pub fn release(self: Held) void {107 pub fn release(self: Held) void {
81 SpinLock.Held.release(SpinLock.Held { .spinlock = &self.mutex.lock });108 SpinLock.Held.release(SpinLock.Held{ .spinlock = &self.mutex.lock });
82 }109 }
83 };110 };
84111
85 pub fn init() Mutex {112 pub fn init() Mutex {
86 return Mutex {113 return Mutex{ .lock = SpinLock.init() };
87 .lock = SpinLock.init(),
88 };
89 }114 }
90115
91 pub fn deinit(self: *Mutex) void {}116 pub fn deinit(self: *Mutex) void {}
92117
93 pub fn acquire(self: *Mutex) Held {118 pub fn acquire(self: *Mutex) Held {
94 _ = self.lock.acquire();119 _ = self.lock.acquire();
95 return Held { .mutex = self };120 return Held{ .mutex = self };
96 }121 }
97 },122 },
98};123};
...@@ -122,15 +147,20 @@ test "std.Mutex" {...@@ -122,15 +147,20 @@ test "std.Mutex" {
122 .data = 0,147 .data = 0,
123 };148 };
124149
125 const thread_count = 10;150 if (builtin.single_threaded) {
126 var threads: [thread_count]*std.os.Thread = undefined;151 worker(&context);
127 for (threads) |*t| {152 std.debug.assertOrPanic(context.data == TestContext.incr_count);
128 t.* = try std.os.spawnThread(&context, worker);153 } else {
129 }154 const thread_count = 10;
130 for (threads) |t|155 var threads: [thread_count]*std.os.Thread = undefined;
131 t.wait();156 for (threads) |*t| {
157 t.* = try std.os.spawnThread(&context, worker);
158 }
159 for (threads) |t|
160 t.wait();
132161
133 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);162 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);
163 }
134}164}
135165
136fn worker(ctx: *TestContext) void {166fn worker(ctx: *TestContext) void {
std/os/index.zig+1
...@@ -3013,6 +3013,7 @@ pub const SpawnThreadError = error{...@@ -3013,6 +3013,7 @@ pub const SpawnThreadError = error{
3013/// where T is u8, noreturn, void, or !void3013/// where T is u8, noreturn, void, or !void
3014/// caller must call wait on the returned thread3014/// caller must call wait on the returned thread
3015pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread {3015pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread {
3016 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
3016 // TODO compile-time call graph analysis to determine stack upper bound3017 // TODO compile-time call graph analysis to determine stack upper bound
3017 // https://github.com/ziglang/zig/issues/1573018 // https://github.com/ziglang/zig/issues/157
3018 const default_stack_size = 8 * 1024 * 1024;3019 const default_stack_size = 8 * 1024 * 1024;
std/os/test.zig+4
...@@ -40,6 +40,8 @@ fn testThreadIdFn(thread_id: *os.Thread.Id) void {...@@ -40,6 +40,8 @@ fn testThreadIdFn(thread_id: *os.Thread.Id) void {
40}40}
4141
42test "std.os.Thread.getCurrentId" {42test "std.os.Thread.getCurrentId" {
43 if (builtin.single_threaded) return error.SkipZigTest;
44
43 var thread_current_id: os.Thread.Id = undefined;45 var thread_current_id: os.Thread.Id = undefined;
44 const thread = try os.spawnThread(&thread_current_id, testThreadIdFn);46 const thread = try os.spawnThread(&thread_current_id, testThreadIdFn);
45 const thread_id = thread.handle();47 const thread_id = thread.handle();
...@@ -53,6 +55,8 @@ test "std.os.Thread.getCurrentId" {...@@ -53,6 +55,8 @@ test "std.os.Thread.getCurrentId" {
53}55}
5456
55test "spawn threads" {57test "spawn threads" {
58 if (builtin.single_threaded) return error.SkipZigTest;
59
56 var shared_ctx: i32 = 1;60 var shared_ctx: i32 = 1;
5761
58 const thread1 = try std.os.spawnThread({}, start1);62 const thread1 = try std.os.spawnThread({}, start1);
std/statically_initialized_mutex.zig+13-8
...@@ -93,13 +93,18 @@ test "std.StaticallyInitializedMutex" {...@@ -93,13 +93,18 @@ test "std.StaticallyInitializedMutex" {
93 .data = 0,93 .data = 0,
94 };94 };
9595
96 const thread_count = 10;96 if (builtin.single_threaded) {
97 var threads: [thread_count]*std.os.Thread = undefined;97 TestContext.worker(&context);
98 for (threads) |*t| {98 std.debug.assertOrPanic(context.data == TestContext.incr_count);
99 t.* = try std.os.spawnThread(&context, TestContext.worker);99 } else {
100 }100 const thread_count = 10;
101 for (threads) |t|101 var threads: [thread_count]*std.os.Thread = undefined;
102 t.wait();102 for (threads) |*t| {
103 t.* = try std.os.spawnThread(&context, TestContext.worker);
104 }
105 for (threads) |t|
106 t.wait();
103107
104 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);108 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);
109 }
105}110}
test/tests.zig+25-18
...@@ -163,25 +163,32 @@ pub fn addPkgTests(b: *build.Builder, test_filter: ?[]const u8, root_src: []cons...@@ -163,25 +163,32 @@ pub fn addPkgTests(b: *build.Builder, test_filter: ?[]const u8, root_src: []cons
163 for (test_targets) |test_target| {163 for (test_targets) |test_target| {
164 const is_native = (test_target.os == builtin.os and test_target.arch == builtin.arch);164 const is_native = (test_target.os == builtin.os and test_target.arch == builtin.arch);
165 for (modes) |mode| {165 for (modes) |mode| {
166 for ([]bool{166 for ([]bool{ false, true }) |link_libc| {
167 false,167 for ([]bool{ false, true }) |single_threaded| {
168 true,168 if (link_libc and !is_native) {
169 }) |link_libc| {169 // don't assume we have a cross-compiling libc set up
170 if (link_libc and !is_native) {170 continue;
171 // don't assume we have a cross-compiling libc set up171 }
172 continue;172 const these_tests = b.addTest(root_src);
173 }173 these_tests.setNamePrefix(b.fmt(
174 const these_tests = b.addTest(root_src);174 "{}-{}-{}-{}-{}-{} ",
175 these_tests.setNamePrefix(b.fmt("{}-{}-{}-{}-{} ", name, @tagName(test_target.os), @tagName(test_target.arch), @tagName(mode), if (link_libc) "c" else "bare"));175 name,
176 these_tests.setFilter(test_filter);176 @tagName(test_target.os),
177 these_tests.setBuildMode(mode);177 @tagName(test_target.arch),
178 if (!is_native) {178 @tagName(mode),
179 these_tests.setTarget(test_target.arch, test_target.os, test_target.environ);179 if (link_libc) "c" else "bare",
180 }180 if (single_threaded) "single" else "multi",
181 if (link_libc) {181 ));
182 these_tests.linkSystemLibrary("c");182 these_tests.setFilter(test_filter);
183 these_tests.setBuildMode(mode);
184 if (!is_native) {
185 these_tests.setTarget(test_target.arch, test_target.os, test_target.environ);
186 }
187 if (link_libc) {
188 these_tests.linkSystemLibrary("c");
189 }
190 step.dependOn(&these_tests.step);
183 }191 }
184 step.dependOn(&these_tests.step);
185 }192 }
186 }193 }
187 }194 }