authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-01 12:22:55-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-01 12:22:55-05:00
log8d32d256198589eeaccb92892e5b3145c097514c
tree6c54dd3a62adafa79233777fa6ca7bccd054bc4f
parent7a4ed10981cf7ac5bed93b2c9bad280ad31c50cf
parentbfc1772d8ead9e8fcde0a315411ddcd040cc3d28

Merge branch 'emekoi-windows-mutex'


7 files changed, 242 insertions(+), 61 deletions(-)

CMakeLists.txt+16-15
......@@ -454,10 +454,10 @@ set(ZIG_STD_FILES
454454 "crypto/hmac.zig"
455455 "crypto/index.zig"
456456 "crypto/md5.zig"
457 "crypto/poly1305.zig"
457458 "crypto/sha1.zig"
458459 "crypto/sha2.zig"
459460 "crypto/sha3.zig"
460 "crypto/poly1305.zig"
461461 "crypto/x25519.zig"
462462 "cstr.zig"
463463 "debug/failing_allocator.zig"
......@@ -566,9 +566,9 @@ set(ZIG_STD_FILES
566566 "math/tan.zig"
567567 "math/tanh.zig"
568568 "math/trunc.zig"
569 "mem.zig"
569570 "meta/index.zig"
570571 "meta/trait.zig"
571 "mem.zig"
572572 "mutex.zig"
573573 "net.zig"
574574 "os/child_process.zig"
......@@ -576,16 +576,16 @@ set(ZIG_STD_FILES
576576 "os/darwin/errno.zig"
577577 "os/epoch.zig"
578578 "os/file.zig"
579 "os/freebsd/errno.zig"
580 "os/freebsd/index.zig"
579581 "os/get_app_data_dir.zig"
580582 "os/get_user_id.zig"
581583 "os/index.zig"
584 "os/linux/arm64.zig"
582585 "os/linux/errno.zig"
583586 "os/linux/index.zig"
584587 "os/linux/vdso.zig"
585588 "os/linux/x86_64.zig"
586 "os/linux/arm64.zig"
587 "os/freebsd/errno.zig"
588 "os/freebsd/index.zig"
589589 "os/path.zig"
590590 "os/time.zig"
591591 "os/uefi.zig"
......@@ -612,6 +612,16 @@ set(ZIG_STD_FILES
612612 "special/compiler_rt/comparetf2.zig"
613613 "special/compiler_rt/divti3.zig"
614614 "special/compiler_rt/extendXfYf2.zig"
615 "special/compiler_rt/fixdfdi.zig"
616 "special/compiler_rt/fixdfsi.zig"
617 "special/compiler_rt/fixdfti.zig"
618 "special/compiler_rt/fixint.zig"
619 "special/compiler_rt/fixsfdi.zig"
620 "special/compiler_rt/fixsfsi.zig"
621 "special/compiler_rt/fixsfti.zig"
622 "special/compiler_rt/fixtfdi.zig"
623 "special/compiler_rt/fixtfsi.zig"
624 "special/compiler_rt/fixtfti.zig"
615625 "special/compiler_rt/fixuint.zig"
616626 "special/compiler_rt/fixunsdfdi.zig"
617627 "special/compiler_rt/fixunsdfsi.zig"
......@@ -622,16 +632,6 @@ set(ZIG_STD_FILES
622632 "special/compiler_rt/fixunstfdi.zig"
623633 "special/compiler_rt/fixunstfsi.zig"
624634 "special/compiler_rt/fixunstfti.zig"
625 "special/compiler_rt/fixint.zig"
626 "special/compiler_rt/fixdfdi.zig"
627 "special/compiler_rt/fixdfsi.zig"
628 "special/compiler_rt/fixdfti.zig"
629 "special/compiler_rt/fixsfdi.zig"
630 "special/compiler_rt/fixsfsi.zig"
631 "special/compiler_rt/fixsfti.zig"
632 "special/compiler_rt/fixtfdi.zig"
633 "special/compiler_rt/fixtfsi.zig"
634 "special/compiler_rt/fixtfti.zig"
635635 "special/compiler_rt/floattidf.zig"
636636 "special/compiler_rt/floattisf.zig"
637637 "special/compiler_rt/floattitf.zig"
......@@ -656,6 +656,7 @@ set(ZIG_STD_FILES
656656 "special/panic.zig"
657657 "special/test_runner.zig"
658658 "spinlock.zig"
659 "statically_initialized_mutex.zig"
659660 "unicode.zig"
660661 "zig/ast.zig"
661662 "zig/index.zig"
std/fmt/index.zig-2
......@@ -982,13 +982,11 @@ test "fmt.format" {
982982 context = BufPrintContext{ .remaining = buf1[0..] };
983983 try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite);
984984 res = buf1[0 .. buf1.len - context.remaining.len];
985 debug.warn("{}\n", res);
986985 assert(mem.eql(u8, res, "a"));
987986
988987 context = BufPrintContext{ .remaining = buf1[0..] };
989988 try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite);
990989 res = buf1[0 .. buf1.len - context.remaining.len];
991 debug.warn("{}\n", res);
992990 assert(mem.eql(u8, res, "1100"));
993991 }
994992 {
std/index.zig+2
......@@ -9,6 +9,7 @@ pub const DynLib = @import("dynamic_library.zig").DynLib;
99pub const HashMap = @import("hash_map.zig").HashMap;
1010pub const LinkedList = @import("linked_list.zig").LinkedList;
1111pub const Mutex = @import("mutex.zig").Mutex;
12pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex;
1213pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
1314pub const SpinLock = @import("spinlock.zig").SpinLock;
1415
......@@ -55,6 +56,7 @@ test "std" {
5556 _ = @import("hash_map.zig");
5657 _ = @import("linked_list.zig");
5758 _ = @import("mutex.zig");
59 _ = @import("statically_initialized_mutex.zig");
5860 _ = @import("segmented_list.zig");
5961 _ = @import("spinlock.zig");
6062
std/mutex.zig+71-44
......@@ -5,74 +5,99 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
55const assert = std.debug.assert;
66const SpinLock = std.SpinLock;
77const linux = std.os.linux;
8const windows = std.os.windows;
89
910/// Lock may be held only once. If the same thread
1011/// tries to acquire the same mutex twice, it deadlocks.
12/// This type must be initialized at runtime, and then deinitialized when no
13/// longer needed, to free resources.
14/// If you need static initialization, use std.StaticallyInitializedMutex.
1115/// The Linux implementation is based on mutex3 from
1216/// https://www.akkadia.org/drepper/futex.pdf
13pub const Mutex = struct {
14 /// 0: unlocked
15 /// 1: locked, no waiters
16 /// 2: locked, one or more waiters
17 linux_lock: @typeOf(linux_lock_init),
18
19 /// TODO better implementation than spin lock
20 spin_lock: @typeOf(spin_lock_init),
21
22 const linux_lock_init = if (builtin.os == builtin.Os.linux) i32(0) else {};
23 const spin_lock_init = if (builtin.os != builtin.Os.linux) SpinLock.init() else {};
24
25 pub const Held = struct {
26 mutex: *Mutex,
27
28 pub fn release(self: Held) void {
29 if (builtin.os == builtin.Os.linux) {
30 const c = @atomicRmw(i32, &self.mutex.linux_lock, AtomicRmwOp.Sub, 1, AtomicOrder.Release);
17pub const Mutex = switch(builtin.os) {
18 builtin.Os.linux => struct {
19 /// 0: unlocked
20 /// 1: locked, no waiters
21 /// 2: locked, one or more waiters
22 lock: i32,
23
24 pub const Held = struct {
25 mutex: *Mutex,
26
27 pub fn release(self: Held) void {
28 const c = @atomicRmw(i32, &self.mutex.lock, AtomicRmwOp.Sub, 1, AtomicOrder.Release);
3129 if (c != 1) {
32 _ = @atomicRmw(i32, &self.mutex.linux_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.Release);
33 const rc = linux.futex_wake(&self.mutex.linux_lock, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1);
30 _ = @atomicRmw(i32, &self.mutex.lock, AtomicRmwOp.Xchg, 0, AtomicOrder.Release);
31 const rc = linux.futex_wake(&self.mutex.lock, linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG, 1);
3432 switch (linux.getErrno(rc)) {
3533 0 => {},
3634 linux.EINVAL => unreachable,
3735 else => unreachable,
3836 }
3937 }
40 } else {
41 SpinLock.Held.release(SpinLock.Held{ .spinlock = &self.mutex.spin_lock });
4238 }
39 };
40
41 pub fn init() Mutex {
42 return Mutex {
43 .lock = 0,
44 };
4345 }
44 };
4546
46 pub fn init() Mutex {
47 return Mutex{
48 .linux_lock = linux_lock_init,
49 .spin_lock = spin_lock_init,
50 };
51 }
47 pub fn deinit(self: *Mutex) void {}
5248
53 pub fn acquire(self: *Mutex) Held {
54 if (builtin.os == builtin.Os.linux) {
55 var c = @cmpxchgWeak(i32, &self.linux_lock, 0, 1, AtomicOrder.Acquire, AtomicOrder.Monotonic) orelse
49 pub fn acquire(self: *Mutex) Held {
50 var c = @cmpxchgWeak(i32, &self.lock, 0, 1, AtomicOrder.Acquire, AtomicOrder.Monotonic) orelse
5651 return Held{ .mutex = self };
5752 if (c != 2)
58 c = @atomicRmw(i32, &self.linux_lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);
53 c = @atomicRmw(i32, &self.lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);
5954 while (c != 0) {
60 const rc = linux.futex_wait(&self.linux_lock, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, 2, null);
55 const rc = linux.futex_wait(&self.lock, linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG, 2, null);
6156 switch (linux.getErrno(rc)) {
6257 0, linux.EINTR, linux.EAGAIN => {},
6358 linux.EINVAL => unreachable,
6459 else => unreachable,
6560 }
66 c = @atomicRmw(i32, &self.linux_lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);
61 c = @atomicRmw(i32, &self.lock, AtomicRmwOp.Xchg, 2, AtomicOrder.Acquire);
6762 }
68 } else {
69 _ = self.spin_lock.acquire();
63 return Held { .mutex = self };
7064 }
71 return Held{ .mutex = self };
72 }
65 },
66 // TODO once https://github.com/ziglang/zig/issues/287 (copy elision) is solved, we can make a
67 // better implementation of this. The problem is we need the init() function to have access to
68 // the address of the CRITICAL_SECTION, and then have it not move.
69 builtin.Os.windows => std.StaticallyInitializedMutex,
70 else => struct {
71 /// TODO better implementation than spin lock.
72 /// When changing this, one must also change the corresponding
73 /// std.StaticallyInitializedMutex code, since it aliases this type,
74 /// under the assumption that it works both statically and at runtime.
75 lock: SpinLock,
76
77 pub const Held = struct {
78 mutex: *Mutex,
79
80 pub fn release(self: Held) void {
81 SpinLock.Held.release(SpinLock.Held { .spinlock = &self.mutex.lock });
82 }
83 };
84
85 pub fn init() Mutex {
86 return Mutex {
87 .lock = SpinLock.init(),
88 };
89 }
90
91 pub fn deinit(self: *Mutex) void {}
92
93 pub fn acquire(self: *Mutex) Held {
94 _ = self.lock.acquire();
95 return Held { .mutex = self };
96 }
97 },
7398};
7499
75const Context = struct {
100const TestContext = struct {
76101 mutex: *Mutex,
77102 data: i128,
78103
......@@ -90,7 +115,9 @@ test "std.Mutex" {
90115 var a = &fixed_buffer_allocator.allocator;
91116
92117 var mutex = Mutex.init();
93 var context = Context{
118 defer mutex.deinit();
119
120 var context = TestContext{
94121 .mutex = &mutex,
95122 .data = 0,
96123 };
......@@ -103,12 +130,12 @@ test "std.Mutex" {
103130 for (threads) |t|
104131 t.wait();
105132
106 std.debug.assertOrPanic(context.data == thread_count * Context.incr_count);
133 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);
107134}
108135
109fn worker(ctx: *Context) void {
136fn worker(ctx: *TestContext) void {
110137 var i: usize = 0;
111 while (i != Context.incr_count) : (i += 1) {
138 while (i != TestContext.incr_count) : (i += 1) {
112139 const held = ctx.mutex.acquire();
113140 defer held.release();
114141
std/os/windows/index.zig+1
......@@ -49,6 +49,7 @@ pub const UNICODE = false;
4949pub const WCHAR = u16;
5050pub const WORD = u16;
5151pub const LARGE_INTEGER = i64;
52pub const LONG = c_long;
5253
5354pub const TRUE = 1;
5455pub const FALSE = 0;
std/os/windows/kernel32.zig+47
......@@ -220,3 +220,50 @@ pub const FOREGROUND_BLUE = 1;
220220pub const FOREGROUND_GREEN = 2;
221221pub const FOREGROUND_RED = 4;
222222pub const FOREGROUND_INTENSITY = 8;
223
224pub extern "kernel32" stdcallcc fn InitializeCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
225pub extern "kernel32" stdcallcc fn EnterCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
226pub extern "kernel32" stdcallcc fn LeaveCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
227pub extern "kernel32" stdcallcc fn DeleteCriticalSection(lpCriticalSection: *CRITICAL_SECTION) void;
228
229pub const LIST_ENTRY = extern struct {
230 Flink: *LIST_ENTRY,
231 Blink: *LIST_ENTRY,
232};
233
234pub const RTL_CRITICAL_SECTION_DEBUG = extern struct {
235 Type: WORD,
236 CreatorBackTraceIndex: WORD,
237 CriticalSection: *RTL_CRITICAL_SECTION,
238 ProcessLocksList: LIST_ENTRY,
239 EntryCount: DWORD,
240 ContentionCount: DWORD,
241 Flags: DWORD,
242 CreatorBackTraceIndexHigh: WORD,
243 SpareWORD: WORD,
244};
245
246pub const RTL_CRITICAL_SECTION = extern struct {
247 DebugInfo: *RTL_CRITICAL_SECTION_DEBUG,
248 LockCount: LONG,
249 RecursionCount: LONG,
250 OwningThread: HANDLE,
251 LockSemaphore: HANDLE,
252 SpinCount: ULONG_PTR,
253};
254
255pub const CRITICAL_SECTION = RTL_CRITICAL_SECTION;
256pub const INIT_ONCE = RTL_RUN_ONCE;
257pub const INIT_ONCE_STATIC_INIT = RTL_RUN_ONCE_INIT;
258
259pub extern "kernel32" stdcallcc fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter: ?*c_void, Context: ?*c_void) BOOL;
260
261pub const INIT_ONCE_FN = extern fn(InitOnce: *INIT_ONCE, Parameter: ?*c_void, Context: ?*c_void) BOOL;
262
263pub const RTL_RUN_ONCE = extern struct {
264 Ptr: ?*c_void,
265};
266
267pub const RTL_RUN_ONCE_INIT = RTL_RUN_ONCE {
268 .Ptr = null,
269};
std/statically_initialized_mutex.zig created+105
......@@ -0,0 +1,105 @@
1const std = @import("index.zig");
2const builtin = @import("builtin");
3const AtomicOrder = builtin.AtomicOrder;
4const AtomicRmwOp = builtin.AtomicRmwOp;
5const assert = std.debug.assert;
6const windows = std.os.windows;
7
8/// Lock may be held only once. If the same thread
9/// tries to acquire the same mutex twice, it deadlocks.
10/// This type is intended to be initialized statically. If you don't
11/// require static initialization, use std.Mutex.
12/// On Windows, this mutex allocates resources when it is
13/// first used, and the resources cannot be freed.
14/// On Linux, this is an alias of std.Mutex.
15pub const StaticallyInitializedMutex = switch(builtin.os) {
16 builtin.Os.linux => std.Mutex,
17 builtin.Os.windows => struct {
18 lock: windows.CRITICAL_SECTION,
19 init_once: windows.RTL_RUN_ONCE,
20
21 pub const Held = struct {
22 mutex: *StaticallyInitializedMutex,
23
24 pub fn release(self: Held) void {
25 windows.LeaveCriticalSection(&self.mutex.lock);
26 }
27 };
28
29 pub fn init() StaticallyInitializedMutex {
30 return StaticallyInitializedMutex {
31 .lock = undefined,
32 .init_once = windows.INIT_ONCE_STATIC_INIT,
33 };
34 }
35
36 extern fn initCriticalSection(
37 InitOnce: *windows.RTL_RUN_ONCE,
38 Parameter: ?*c_void,
39 Context: ?*c_void,
40 ) windows.BOOL {
41 const lock = @ptrCast(*windows.CRITICAL_SECTION, @alignCast(@alignOf(windows.CRITICAL_SECTION), Parameter));
42 windows.InitializeCriticalSection(lock);
43 return windows.TRUE;
44 }
45
46 /// TODO: once https://github.com/ziglang/zig/issues/287 is solved and std.Mutex has a better
47 /// implementation of a runtime initialized mutex, remove this function.
48 pub fn deinit(self: *StaticallyInitializedMutex) void {
49 assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
50 windows.DeleteCriticalSection(&self.lock);
51 }
52
53 pub fn acquire(self: *StaticallyInitializedMutex) Held {
54 assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
55 windows.EnterCriticalSection(&self.lock);
56 return Held { .mutex = self };
57 }
58 },
59 else => std.Mutex,
60};
61
62test "std.StaticallyInitializedMutex" {
63 const TestContext = struct {
64 data: i128,
65
66 const TestContext = @This();
67 const incr_count = 10000;
68
69 var mutex = StaticallyInitializedMutex.init();
70
71 fn worker(ctx: *TestContext) void {
72 var i: usize = 0;
73 while (i != TestContext.incr_count) : (i += 1) {
74 const held = mutex.acquire();
75 defer held.release();
76
77 ctx.data += 1;
78 }
79 }
80 };
81
82 var direct_allocator = std.heap.DirectAllocator.init();
83 defer direct_allocator.deinit();
84
85 var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024);
86 defer direct_allocator.allocator.free(plenty_of_memory);
87
88 var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory);
89 var a = &fixed_buffer_allocator.allocator;
90
91
92 var context = TestContext{
93 .data = 0,
94 };
95
96 const thread_count = 10;
97 var threads: [thread_count]*std.os.Thread = undefined;
98 for (threads) |*t| {
99 t.* = try std.os.spawnThread(&context, TestContext.worker);
100 }
101 for (threads) |t|
102 t.wait();
103
104 std.debug.assertOrPanic(context.data == thread_count * TestContext.incr_count);
105}