| author | |
| committer | |
| log | 9b8e23934bc87f1fd6a42cdfdd551212994b6e58 |
| tree | 0ac63357b6504f7bf44e66002b0ccca49dc64b45 |
| parent | 8bedb10939b511e39787b2693249d2d3e8102854 |
| signature | Commit is signed but in an unrecognized format. |
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 #190818 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 is | 6738 | 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); |
| 122 | 123 | ||
| 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; | ||
| 396 | 398 | ||
| 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 | }; |
| 172 | 172 | ||
| 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 | } | ||
| 181 | 196 | ||
| 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 | } | ||
| 187 | 203 | ||
| 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; |
| 4 | 4 | ||
| 5 | /// Many reader, many writer, non-allocating, thread-safe | 5 | /// 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. | ||
| 7 | pub fn Stack(comptime T: type) type { | 8 | pub 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); | ||
| 11 | 14 | ||
| 12 | pub const Self = @This(); | 15 | pub const Self = @This(); |
| 13 | 16 | ||
| ... | @@ -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 | } |
| 25 | 28 | ||
| ... | @@ -31,20 +34,31 @@ pub fn Stack(comptime T: type) type { | ... | @@ -31,20 +34,31 @@ pub fn Stack(comptime T: type) type { |
| 31 | } | 34 | } |
| 32 | 35 | ||
| 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; |
| 36 | 39 | 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 | } |
| 40 | 48 | ||
| 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; |
| 44 | 52 | 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 | } |
| 49 | 63 | ||
| 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 | }; |
| 92 | 106 | ||
| 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 | } | ||
| 101 | 130 | ||
| 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 | } | ||
| 107 | 137 | ||
| 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 | } |
| 320 | 320 | ||
| 321 | test "std.event.Channel" { | 321 | test "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(); |
| 324 | 327 |
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 | } |
| 85 | 85 | ||
| 86 | test "std.event.Future" { | 86 | test "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(); |
| 89 | 92 |
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 | } |
| 122 | 122 | ||
| 123 | test "std.event.Group" { | 123 | test "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(); |
| 126 | 129 |
std/event/lock.zig+3| ... | @@ -122,6 +122,9 @@ pub const Lock = struct { | ... | @@ -122,6 +122,9 @@ pub const Lock = struct { |
| 122 | }; | 122 | }; |
| 123 | 123 | ||
| 124 | test "std.event.Lock" { | 124 | test "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(); |
| 127 | 130 |
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 *Loop | 97 | /// 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 | } |
| 203 | 204 | ||
| 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 fail | 212 | // 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 | } |
| 303 | 309 | ||
| 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 | } |
| 340 | 351 | ||
| 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 | }; |
| 846 | 862 | ||
| 847 | test "std.event.Loop - basic" { | 863 | test "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(); |
| 850 | 869 | ||
| ... | @@ -858,6 +877,9 @@ test "std.event.Loop - basic" { | ... | @@ -858,6 +877,9 @@ test "std.event.Loop - basic" { |
| 858 | } | 877 | } |
| 859 | 878 | ||
| 860 | test "std.event.Loop - call" { | 879 | test "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(); |
| 863 | 885 |
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 | } |
| 270 | 270 | ||
| 271 | test "listen on a port, send bytes, receive bytes" { | 271 | test "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 systems | 276 | // 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 | }; |
| 212 | 212 | ||
| 213 | test "std.event.RwLock" { | 213 | test "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(); |
| 216 | 219 |
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 from | 15 | /// The Linux implementation is based on mutex3 from |
| 16 | /// https://www.akkadia.org/drepper/futex.pdf | 16 | /// https://www.akkadia.org/drepper/futex.pdf |
| 17 | pub 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. | ||
| 19 | pub 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 | } | ||
| 46 | else switch (builtin.os) { | ||
| 18 | builtin.Os.linux => struct { | 47 | builtin.Os.linux => struct { |
| 19 | /// 0: unlocked | 48 | /// 0: unlocked |
| 20 | /// 1: locked, no waiters | 49 | /// 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 | }; |
| 40 | 69 | ||
| 41 | pub fn init() Mutex { | 70 | pub fn init() Mutex { |
| 42 | return Mutex { | 71 | return Mutex{ .lock = 0 }; |
| 43 | .lock = 0, | ||
| 44 | }; | ||
| 45 | } | 72 | } |
| 46 | 73 | ||
| 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 a | 93 | // 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, |
| 79 | 106 | ||
| 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 | }; |
| 84 | 111 | ||
| 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 | } |
| 90 | 115 | ||
| 91 | pub fn deinit(self: *Mutex) void {} | 116 | pub fn deinit(self: *Mutex) void {} |
| 92 | 117 | ||
| 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 | }; |
| 124 | 149 | ||
| 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(); | ||
| 132 | 161 | ||
| 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 | } |
| 135 | 165 | ||
| 136 | fn worker(ctx: *TestContext) void { | 166 | fn 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 !void | 3013 | /// where T is u8, noreturn, void, or !void |
| 3014 | /// caller must call wait on the returned thread | 3014 | /// caller must call wait on the returned thread |
| 3015 | pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread { | 3015 | pub 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 bound | 3017 | // TODO compile-time call graph analysis to determine stack upper bound |
| 3017 | // https://github.com/ziglang/zig/issues/157 | 3018 | // 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 | } |
| 41 | 41 | ||
| 42 | test "std.os.Thread.getCurrentId" { | 42 | test "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 | } |
| 54 | 56 | ||
| 55 | test "spawn threads" { | 57 | test "spawn threads" { |
| 58 | if (builtin.single_threaded) return error.SkipZigTest; | ||
| 59 | |||
| 56 | var shared_ctx: i32 = 1; | 60 | var shared_ctx: i32 = 1; |
| 57 | 61 | ||
| 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 | }; |
| 95 | 95 | ||
| 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(); | ||
| 103 | 107 | ||
| 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 up | 171 | } |
| 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 | } |