Skip to content

Commit 9b8e239

Browse files
committed
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
1 parent 8bedb10 commit 9b8e239

18 files changed

+246
-83
lines changed

doc/langref.html.in

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6714,6 +6714,25 @@ pub fn build(b: *Builder) void {
67146714
{#header_close#}
67156715
{#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
67166716
{#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+
67176736
{#header_open|Undefined Behavior#}
67186737
<p>
67196738
Zig has many instances of undefined behavior. If undefined behavior is

src/all_types.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,7 @@ struct CodeGen {
18081808
bool is_static;
18091809
bool strip_debug_symbols;
18101810
bool is_test_build;
1811+
bool is_single_threaded;
18111812
bool is_native_target;
18121813
bool linker_rdynamic;
18131814
bool no_rosegment_workaround;

src/codegen.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out
118118
g->string_literals_table.init(16);
119119
g->type_info_cache.init(32);
120120
g->is_test_build = false;
121+
g->is_single_threaded = false;
121122
buf_resize(&g->global_asm, 0);
122123

123124
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) {
73777378
buf_appendf(contents, "pub const endian = %s;\n", endian_str);
73787379
}
73797380
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));
73807382
buf_appendf(contents, "pub const os = Os.%s;\n", cur_os);
73817383
buf_appendf(contents, "pub const arch = Arch.%s;\n", cur_arch);
73827384
buf_appendf(contents, "pub const environ = Environ.%s;\n", cur_environ);
@@ -7411,6 +7413,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
74117413
cache_buf(&cache_hash, compiler_id);
74127414
cache_int(&cache_hash, g->build_mode);
74137415
cache_bool(&cache_hash, g->is_test_build);
7416+
cache_bool(&cache_hash, g->is_single_threaded);
74147417
cache_int(&cache_hash, g->zig_target.arch.arch);
74157418
cache_int(&cache_hash, g->zig_target.arch.sub_arch);
74167419
cache_int(&cache_hash, g->zig_target.vendor);
@@ -8329,6 +8332,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
83298332
cache_bool(ch, g->is_static);
83308333
cache_bool(ch, g->strip_debug_symbols);
83318334
cache_bool(ch, g->is_test_build);
8335+
cache_bool(ch, g->is_single_threaded);
83328336
cache_bool(ch, g->is_native_target);
83338337
cache_bool(ch, g->linker_rdynamic);
83348338
cache_bool(ch, g->no_rosegment_workaround);

src/main.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ static int print_full_usage(const char *arg0) {
5959
" --release-fast build with optimizations on and safety off\n"
6060
" --release-safe build with optimizations on and safety on\n"
6161
" --release-small build with size optimizations on and safety off\n"
62+
" --single-threaded source may assume it is only used single-threaded\n"
6263
" --static output will be statically linked\n"
6364
" --strip exclude debug symbols\n"
6465
" --target-arch [name] specify target architecture\n"
@@ -393,6 +394,7 @@ int main(int argc, char **argv) {
393394
bool no_rosegment_workaround = false;
394395
bool system_linker_hack = false;
395396
TargetSubsystem subsystem = TargetSubsystemAuto;
397+
bool is_single_threaded = false;
396398

397399
if (argc >= 2 && strcmp(argv[1], "build") == 0) {
398400
Buf zig_exe_path_buf = BUF_INIT;
@@ -550,6 +552,8 @@ int main(int argc, char **argv) {
550552
disable_pic = true;
551553
} else if (strcmp(arg, "--system-linker-hack") == 0) {
552554
system_linker_hack = true;
555+
} else if (strcmp(arg, "--single-threaded") == 0) {
556+
is_single_threaded = true;
553557
} else if (strcmp(arg, "--test-cmd-bin") == 0) {
554558
test_exec_args.append(nullptr);
555559
} else if (arg[1] == 'L' && arg[2] != 0) {
@@ -816,6 +820,7 @@ int main(int argc, char **argv) {
816820
switch (cmd) {
817821
case CmdBuiltin: {
818822
CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir());
823+
g->is_single_threaded = is_single_threaded;
819824
Buf *builtin_source = codegen_generate_builtin_source(g);
820825
if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {
821826
fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));
@@ -889,6 +894,7 @@ int main(int argc, char **argv) {
889894
codegen_set_out_name(g, buf_out_name);
890895
codegen_set_lib_version(g, ver_major, ver_minor, ver_patch);
891896
codegen_set_is_test(g, cmd == CmdTest);
897+
g->is_single_threaded = is_single_threaded;
892898
codegen_set_linker_script(g, linker_script);
893899
if (each_lib_rpath)
894900
codegen_set_each_lib_rpath(g, each_lib_rpath);

std/atomic/queue.zig

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -170,20 +170,36 @@ test "std.atomic.Queue" {
170170
.get_count = 0,
171171
};
172172

173-
var putters: [put_thread_count]*std.os.Thread = undefined;
174-
for (putters) |*t| {
175-
t.* = try std.os.spawnThread(&context, startPuts);
176-
}
177-
var getters: [put_thread_count]*std.os.Thread = undefined;
178-
for (getters) |*t| {
179-
t.* = try std.os.spawnThread(&context, startGets);
180-
}
173+
if (builtin.single_threaded) {
174+
{
175+
var i: usize = 0;
176+
while (i < put_thread_count) : (i += 1) {
177+
std.debug.assertOrPanic(startPuts(&context) == 0);
178+
}
179+
}
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|
183-
t.wait();
184-
_ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
185-
for (getters) |t|
186-
t.wait();
197+
for (putters) |t|
198+
t.wait();
199+
_ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
200+
for (getters) |t|
201+
t.wait();
202+
}
187203

188204
if (context.put_sum != context.get_sum) {
189205
std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum);

std/atomic/stack.zig

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ const AtomicOrder = builtin.AtomicOrder;
44

55
/// Many reader, many writer, non-allocating, thread-safe
66
/// Uses a spinlock to protect push() and pop()
7+
/// When building in single threaded mode, this is a simple linked list.
78
pub fn Stack(comptime T: type) type {
89
return struct {
910
root: ?*Node,
10-
lock: u8,
11+
lock: @typeOf(lock_init),
12+
13+
const lock_init = if (builtin.single_threaded) {} else u8(0);
1114

1215
pub const Self = @This();
1316

@@ -19,7 +22,7 @@ pub fn Stack(comptime T: type) type {
1922
pub fn init() Self {
2023
return Self{
2124
.root = null,
22-
.lock = 0,
25+
.lock = lock_init,
2326
};
2427
}
2528

@@ -31,20 +34,31 @@ pub fn Stack(comptime T: type) type {
3134
}
3235

3336
pub fn push(self: *Self, node: *Node) void {
34-
while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
35-
defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
36-
37-
node.next = self.root;
38-
self.root = node;
37+
if (builtin.single_threaded) {
38+
node.next = self.root;
39+
self.root = node;
40+
} else {
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+
}
3947
}
4048

4149
pub fn pop(self: *Self) ?*Node {
42-
while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
43-
defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
44-
45-
const root = self.root orelse return null;
46-
self.root = root.next;
47-
return root;
50+
if (builtin.single_threaded) {
51+
const root = self.root orelse return null;
52+
self.root = root.next;
53+
return root;
54+
} else {
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+
}
4862
}
4963

5064
pub fn isEmpty(self: *Self) bool {
@@ -90,20 +104,36 @@ test "std.atomic.stack" {
90104
.get_count = 0,
91105
};
92106

93-
var putters: [put_thread_count]*std.os.Thread = undefined;
94-
for (putters) |*t| {
95-
t.* = try std.os.spawnThread(&context, startPuts);
96-
}
97-
var getters: [put_thread_count]*std.os.Thread = undefined;
98-
for (getters) |*t| {
99-
t.* = try std.os.spawnThread(&context, startGets);
100-
}
107+
if (builtin.single_threaded) {
108+
{
109+
var i: usize = 0;
110+
while (i < put_thread_count) : (i += 1) {
111+
std.debug.assertOrPanic(startPuts(&context) == 0);
112+
}
113+
}
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|
103-
t.wait();
104-
_ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
105-
for (getters) |t|
106-
t.wait();
131+
for (putters) |t|
132+
t.wait();
133+
_ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
134+
for (getters) |t|
135+
t.wait();
136+
}
107137

108138
if (context.put_sum != context.get_sum) {
109139
std.debug.panic("failure\nput_sum:{} != get_sum:{}", context.put_sum, context.get_sum);

std/event/channel.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ pub fn Channel(comptime T: type) type {
319319
}
320320

321321
test "std.event.Channel" {
322+
// https://github.com/ziglang/zig/issues/1908
323+
if (builtin.single_threaded) return error.SkipZigTest;
324+
322325
var da = std.heap.DirectAllocator.init();
323326
defer da.deinit();
324327

std/event/future.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ pub fn Future(comptime T: type) type {
8484
}
8585

8686
test "std.event.Future" {
87+
// https://github.com/ziglang/zig/issues/1908
88+
if (builtin.single_threaded) return error.SkipZigTest;
89+
8790
var da = std.heap.DirectAllocator.init();
8891
defer da.deinit();
8992

std/event/group.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ pub fn Group(comptime ReturnType: type) type {
121121
}
122122

123123
test "std.event.Group" {
124+
// https://github.com/ziglang/zig/issues/1908
125+
if (builtin.single_threaded) return error.SkipZigTest;
126+
124127
var da = std.heap.DirectAllocator.init();
125128
defer da.deinit();
126129

std/event/lock.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ pub const Lock = struct {
122122
};
123123

124124
test "std.event.Lock" {
125+
// https://github.com/ziglang/zig/issues/1908
126+
if (builtin.single_threaded) return error.SkipZigTest;
127+
125128
var da = std.heap.DirectAllocator.init();
126129
defer da.deinit();
127130

std/event/loop.zig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub const Loop = struct {
9797
/// TODO copy elision / named return values so that the threads referencing *Loop
9898
/// have the correct pointer value.
9999
pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {
100+
if (builtin.single_threaded) @compileError("initMultiThreaded unavailable when building in single-threaded mode");
100101
const core_count = try os.cpuCount(allocator);
101102
return self.initInternal(allocator, core_count);
102103
}
@@ -201,6 +202,11 @@ pub const Loop = struct {
201202
self.os_data.fs_thread.wait();
202203
}
203204

205+
if (builtin.single_threaded) {
206+
assert(extra_thread_count == 0);
207+
return;
208+
}
209+
204210
var extra_thread_index: usize = 0;
205211
errdefer {
206212
// writing 8 bytes to an eventfd cannot fail
@@ -301,6 +307,11 @@ pub const Loop = struct {
301307
self.os_data.fs_thread.wait();
302308
}
303309

310+
if (builtin.single_threaded) {
311+
assert(extra_thread_count == 0);
312+
return;
313+
}
314+
304315
var extra_thread_index: usize = 0;
305316
errdefer {
306317
_ = os.bsdKEvent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;
@@ -338,6 +349,11 @@ pub const Loop = struct {
338349
self.available_eventfd_resume_nodes.push(eventfd_node);
339350
}
340351

352+
if (builtin.single_threaded) {
353+
assert(extra_thread_count == 0);
354+
return;
355+
}
356+
341357
var extra_thread_index: usize = 0;
342358
errdefer {
343359
var i: usize = 0;
@@ -845,6 +861,9 @@ pub const Loop = struct {
845861
};
846862

847863
test "std.event.Loop - basic" {
864+
// https://github.com/ziglang/zig/issues/1908
865+
if (builtin.single_threaded) return error.SkipZigTest;
866+
848867
var da = std.heap.DirectAllocator.init();
849868
defer da.deinit();
850869

@@ -858,6 +877,9 @@ test "std.event.Loop - basic" {
858877
}
859878

860879
test "std.event.Loop - call" {
880+
// https://github.com/ziglang/zig/issues/1908
881+
if (builtin.single_threaded) return error.SkipZigTest;
882+
861883
var da = std.heap.DirectAllocator.init();
862884
defer da.deinit();
863885

std/event/net.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !os.File {
269269
}
270270

271271
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+
272275
if (builtin.os != builtin.Os.linux) {
273276
// TODO build abstractions for other operating systems
274277
return error.SkipZigTest;

std/event/rwlock.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ pub const RwLock = struct {
211211
};
212212

213213
test "std.event.RwLock" {
214+
// https://github.com/ziglang/zig/issues/1908
215+
if (builtin.single_threaded) return error.SkipZigTest;
216+
214217
var da = std.heap.DirectAllocator.init();
215218
defer da.deinit();
216219

0 commit comments

Comments
 (0)