| author | |
| committer | |
| log | f37e79e720215a3a41c603fe41d05d6910c79de2 |
| tree | 551877ba83cb7f5abc731efd25a21ab7fe55b170 |
| parent | 0bb054e5e7ccb164ea1649608d2f6e4195519cb2 |
| parent | c76b0a845fb4176479c8bbf915e57dbdfdb7a594 |
| signature |
Atomic stack and queue16 files changed, 527 insertions(+), 90 deletions(-)
CMakeLists.txt+3| ... | @@ -415,6 +415,9 @@ set(ZIG_CPP_SOURCES | ... | @@ -415,6 +415,9 @@ set(ZIG_CPP_SOURCES |
| 415 | 415 | ||
| 416 | set(ZIG_STD_FILES | 416 | set(ZIG_STD_FILES |
| 417 | "array_list.zig" | 417 | "array_list.zig" |
| 418 | "atomic/index.zig" | ||
| 419 | "atomic/stack.zig" | ||
| 420 | "atomic/queue.zig" | ||
| 418 | "base64.zig" | 421 | "base64.zig" |
| 419 | "buf_map.zig" | 422 | "buf_map.zig" |
| 420 | "buf_set.zig" | 423 | "buf_set.zig" |
src/ir.cpp+5| ... | @@ -18184,6 +18184,11 @@ static TypeTableEntry *ir_analyze_instruction_atomic_rmw(IrAnalyze *ira, IrInstr | ... | @@ -18184,6 +18184,11 @@ static TypeTableEntry *ir_analyze_instruction_atomic_rmw(IrAnalyze *ira, IrInstr |
| 18184 | } else { | 18184 | } else { |
| 18185 | if (!ir_resolve_atomic_order(ira, instruction->ordering->other, &ordering)) | 18185 | if (!ir_resolve_atomic_order(ira, instruction->ordering->other, &ordering)) |
| 18186 | return ira->codegen->builtin_types.entry_invalid; | 18186 | return ira->codegen->builtin_types.entry_invalid; |
| 18187 | if (ordering == AtomicOrderUnordered) { | ||
| 18188 | ir_add_error(ira, instruction->ordering, | ||
| 18189 | buf_sprintf("@atomicRmw atomic ordering must not be Unordered")); | ||
| 18190 | return ira->codegen->builtin_types.entry_invalid; | ||
| 18191 | } | ||
| 18187 | } | 18192 | } |
| 18188 | 18193 | ||
| 18189 | if (instr_is_comptime(casted_operand) && instr_is_comptime(casted_ptr) && casted_ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar) | 18194 | if (instr_is_comptime(casted_operand) && instr_is_comptime(casted_ptr) && casted_ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar) |
std/atomic/index.zig created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | pub const Stack = @import("stack.zig").Stack; | ||
| 2 | pub const Queue = @import("queue.zig").Queue; | ||
| 3 | |||
| 4 | test "std.atomic" { | ||
| 5 | _ = @import("stack.zig").Stack; | ||
| 6 | _ = @import("queue.zig").Queue; | ||
| 7 | } | ||
std/atomic/queue.zig created+120| ... | @@ -0,0 +1,120 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const AtomicOrder = builtin.AtomicOrder; | ||
| 3 | const AtomicRmwOp = builtin.AtomicRmwOp; | ||
| 4 | |||
| 5 | /// Many reader, many writer, non-allocating, thread-safe, lock-free | ||
| 6 | pub fn Queue(comptime T: type) type { | ||
| 7 | return struct { | ||
| 8 | head: &Node, | ||
| 9 | tail: &Node, | ||
| 10 | root: Node, | ||
| 11 | |||
| 12 | pub const Self = this; | ||
| 13 | |||
| 14 | pub const Node = struct { | ||
| 15 | next: ?&Node, | ||
| 16 | data: T, | ||
| 17 | }; | ||
| 18 | |||
| 19 | // TODO: well defined copy elision: https://github.com/zig-lang/zig/issues/287 | ||
| 20 | pub fn init(self: &Self) void { | ||
| 21 | self.root.next = null; | ||
| 22 | self.head = &self.root; | ||
| 23 | self.tail = &self.root; | ||
| 24 | } | ||
| 25 | |||
| 26 | pub fn put(self: &Self, node: &Node) void { | ||
| 27 | node.next = null; | ||
| 28 | |||
| 29 | const tail = @atomicRmw(&Node, &self.tail, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); | ||
| 30 | _ = @atomicRmw(?&Node, &tail.next, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); | ||
| 31 | } | ||
| 32 | |||
| 33 | pub fn get(self: &Self) ?&Node { | ||
| 34 | var head = @atomicLoad(&Node, &self.head, AtomicOrder.Acquire); | ||
| 35 | while (true) { | ||
| 36 | const node = head.next ?? return null; | ||
| 37 | head = @cmpxchgWeak(&Node, &self.head, head, node, AtomicOrder.Release, AtomicOrder.Acquire) ?? return node; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | }; | ||
| 41 | } | ||
| 42 | |||
| 43 | const std = @import("std"); | ||
| 44 | const Context = struct { | ||
| 45 | allocator: &std.mem.Allocator, | ||
| 46 | queue: &Queue(i32), | ||
| 47 | put_sum: isize, | ||
| 48 | get_sum: isize, | ||
| 49 | get_count: usize, | ||
| 50 | puts_done: u8, // TODO make this a bool | ||
| 51 | }; | ||
| 52 | const puts_per_thread = 10000; | ||
| 53 | const put_thread_count = 3; | ||
| 54 | |||
| 55 | test "std.atomic.queue" { | ||
| 56 | var direct_allocator = std.heap.DirectAllocator.init(); | ||
| 57 | defer direct_allocator.deinit(); | ||
| 58 | |||
| 59 | var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 64 * 1024 * 1024); | ||
| 60 | defer direct_allocator.allocator.free(plenty_of_memory); | ||
| 61 | |||
| 62 | var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); | ||
| 63 | var a = &fixed_buffer_allocator.allocator; | ||
| 64 | |||
| 65 | var queue: Queue(i32) = undefined; | ||
| 66 | queue.init(); | ||
| 67 | var context = Context { | ||
| 68 | .allocator = a, | ||
| 69 | .queue = &queue, | ||
| 70 | .put_sum = 0, | ||
| 71 | .get_sum = 0, | ||
| 72 | .puts_done = 0, | ||
| 73 | .get_count = 0, | ||
| 74 | }; | ||
| 75 | |||
| 76 | var putters: [put_thread_count]&std.os.Thread = undefined; | ||
| 77 | for (putters) |*t| { | ||
| 78 | *t = try std.os.spawnThread(&context, startPuts); | ||
| 79 | } | ||
| 80 | var getters: [put_thread_count]&std.os.Thread = undefined; | ||
| 81 | for (getters) |*t| { | ||
| 82 | *t = try std.os.spawnThread(&context, startGets); | ||
| 83 | } | ||
| 84 | |||
| 85 | for (putters) |t| t.wait(); | ||
| 86 | _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | ||
| 87 | for (getters) |t| t.wait(); | ||
| 88 | |||
| 89 | std.debug.assert(context.put_sum == context.get_sum); | ||
| 90 | std.debug.assert(context.get_count == puts_per_thread * put_thread_count); | ||
| 91 | } | ||
| 92 | |||
| 93 | fn startPuts(ctx: &Context) u8 { | ||
| 94 | var put_count: usize = puts_per_thread; | ||
| 95 | var r = std.rand.DefaultPrng.init(0xdeadbeef); | ||
| 96 | while (put_count != 0) : (put_count -= 1) { | ||
| 97 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | ||
| 98 | const x = @bitCast(i32, r.random.scalar(u32)); | ||
| 99 | const node = ctx.allocator.create(Queue(i32).Node) catch unreachable; | ||
| 100 | node.data = x; | ||
| 101 | ctx.queue.put(node); | ||
| 102 | _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); | ||
| 103 | } | ||
| 104 | return 0; | ||
| 105 | } | ||
| 106 | |||
| 107 | fn startGets(ctx: &Context) u8 { | ||
| 108 | while (true) { | ||
| 109 | while (ctx.queue.get()) |node| { | ||
| 110 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | ||
| 111 | _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); | ||
| 112 | _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); | ||
| 113 | } | ||
| 114 | |||
| 115 | if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) { | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | return 0; | ||
| 120 | } | ||
std/atomic/stack.zig created+126| ... | @@ -0,0 +1,126 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const AtomicOrder = builtin.AtomicOrder; | ||
| 3 | |||
| 4 | /// Many reader, many writer, non-allocating, thread-safe, lock-free | ||
| 5 | pub fn Stack(comptime T: type) type { | ||
| 6 | return struct { | ||
| 7 | root: ?&Node, | ||
| 8 | |||
| 9 | pub const Self = this; | ||
| 10 | |||
| 11 | pub const Node = struct { | ||
| 12 | next: ?&Node, | ||
| 13 | data: T, | ||
| 14 | }; | ||
| 15 | |||
| 16 | pub fn init() Self { | ||
| 17 | return Self { | ||
| 18 | .root = null, | ||
| 19 | }; | ||
| 20 | } | ||
| 21 | |||
| 22 | /// push operation, but only if you are the first item in the stack. if you did not succeed in | ||
| 23 | /// being the first item in the stack, returns the other item that was there. | ||
| 24 | pub fn pushFirst(self: &Self, node: &Node) ?&Node { | ||
| 25 | node.next = null; | ||
| 26 | return @cmpxchgStrong(?&Node, &self.root, null, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst); | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn push(self: &Self, node: &Node) void { | ||
| 30 | var root = @atomicLoad(?&Node, &self.root, AtomicOrder.SeqCst); | ||
| 31 | while (true) { | ||
| 32 | node.next = root; | ||
| 33 | root = @cmpxchgWeak(?&Node, &self.root, root, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst) ?? break; | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | pub fn pop(self: &Self) ?&Node { | ||
| 38 | var root = @atomicLoad(?&Node, &self.root, AtomicOrder.Acquire); | ||
| 39 | while (true) { | ||
| 40 | root = @cmpxchgWeak(?&Node, &self.root, root, (root ?? return null).next, AtomicOrder.SeqCst, AtomicOrder.SeqCst) ?? return root; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | pub fn isEmpty(self: &Self) bool { | ||
| 45 | return @atomicLoad(?&Node, &self.root, AtomicOrder.SeqCst) == null; | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | } | ||
| 49 | |||
| 50 | const std = @import("std"); | ||
| 51 | const Context = struct { | ||
| 52 | allocator: &std.mem.Allocator, | ||
| 53 | stack: &Stack(i32), | ||
| 54 | put_sum: isize, | ||
| 55 | get_sum: isize, | ||
| 56 | get_count: usize, | ||
| 57 | puts_done: u8, // TODO make this a bool | ||
| 58 | }; | ||
| 59 | const puts_per_thread = 1000; | ||
| 60 | const put_thread_count = 3; | ||
| 61 | |||
| 62 | test "std.atomic.stack" { | ||
| 63 | var direct_allocator = std.heap.DirectAllocator.init(); | ||
| 64 | defer direct_allocator.deinit(); | ||
| 65 | |||
| 66 | var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 64 * 1024 * 1024); | ||
| 67 | defer direct_allocator.allocator.free(plenty_of_memory); | ||
| 68 | |||
| 69 | var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); | ||
| 70 | var a = &fixed_buffer_allocator.allocator; | ||
| 71 | |||
| 72 | var stack = Stack(i32).init(); | ||
| 73 | var context = Context { | ||
| 74 | .allocator = a, | ||
| 75 | .stack = &stack, | ||
| 76 | .put_sum = 0, | ||
| 77 | .get_sum = 0, | ||
| 78 | .puts_done = 0, | ||
| 79 | .get_count = 0, | ||
| 80 | }; | ||
| 81 | |||
| 82 | var putters: [put_thread_count]&std.os.Thread = undefined; | ||
| 83 | for (putters) |*t| { | ||
| 84 | *t = try std.os.spawnThread(&context, startPuts); | ||
| 85 | } | ||
| 86 | var getters: [put_thread_count]&std.os.Thread = undefined; | ||
| 87 | for (getters) |*t| { | ||
| 88 | *t = try std.os.spawnThread(&context, startGets); | ||
| 89 | } | ||
| 90 | |||
| 91 | for (putters) |t| t.wait(); | ||
| 92 | _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | ||
| 93 | for (getters) |t| t.wait(); | ||
| 94 | |||
| 95 | std.debug.assert(context.put_sum == context.get_sum); | ||
| 96 | std.debug.assert(context.get_count == puts_per_thread * put_thread_count); | ||
| 97 | } | ||
| 98 | |||
| 99 | fn startPuts(ctx: &Context) u8 { | ||
| 100 | var put_count: usize = puts_per_thread; | ||
| 101 | var r = std.rand.DefaultPrng.init(0xdeadbeef); | ||
| 102 | while (put_count != 0) : (put_count -= 1) { | ||
| 103 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | ||
| 104 | const x = @bitCast(i32, r.random.scalar(u32)); | ||
| 105 | const node = ctx.allocator.create(Stack(i32).Node) catch unreachable; | ||
| 106 | node.data = x; | ||
| 107 | ctx.stack.push(node); | ||
| 108 | _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); | ||
| 109 | } | ||
| 110 | return 0; | ||
| 111 | } | ||
| 112 | |||
| 113 | fn startGets(ctx: &Context) u8 { | ||
| 114 | while (true) { | ||
| 115 | while (ctx.stack.pop()) |node| { | ||
| 116 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz | ||
| 117 | _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); | ||
| 118 | _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); | ||
| 119 | } | ||
| 120 | |||
| 121 | if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) { | ||
| 122 | break; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | return 0; | ||
| 126 | } | ||
std/c/darwin.zig+5| ... | @@ -81,3 +81,8 @@ pub const sockaddr = extern struct { | ... | @@ -81,3 +81,8 @@ pub const sockaddr = extern struct { |
| 81 | }; | 81 | }; |
| 82 | 82 | ||
| 83 | pub const sa_family_t = u8; | 83 | pub const sa_family_t = u8; |
| 84 | |||
| 85 | pub const pthread_attr_t = extern struct { | ||
| 86 | __sig: c_long, | ||
| 87 | __opaque: [56]u8, | ||
| 88 | }; |
std/c/index.zig+10| ... | @@ -53,3 +53,13 @@ pub extern "c" fn malloc(usize) ?&c_void; | ... | @@ -53,3 +53,13 @@ pub extern "c" fn malloc(usize) ?&c_void; |
| 53 | pub extern "c" fn realloc(&c_void, usize) ?&c_void; | 53 | pub extern "c" fn realloc(&c_void, usize) ?&c_void; |
| 54 | pub extern "c" fn free(&c_void) void; | 54 | pub extern "c" fn free(&c_void) void; |
| 55 | pub extern "c" fn posix_memalign(memptr: &&c_void, alignment: usize, size: usize) c_int; | 55 | pub extern "c" fn posix_memalign(memptr: &&c_void, alignment: usize, size: usize) c_int; |
| 56 | |||
| 57 | pub extern "pthread" fn pthread_create(noalias newthread: &pthread_t, | ||
| 58 | noalias attr: ?&const pthread_attr_t, start_routine: extern fn(?&c_void) ?&c_void, | ||
| 59 | noalias arg: ?&c_void) c_int; | ||
| 60 | pub extern "pthread" fn pthread_attr_init(attr: &pthread_attr_t) c_int; | ||
| 61 | pub extern "pthread" fn pthread_attr_setstack(attr: &pthread_attr_t, stackaddr: &c_void, stacksize: usize) c_int; | ||
| 62 | pub extern "pthread" fn pthread_attr_destroy(attr: &pthread_attr_t) c_int; | ||
| 63 | pub extern "pthread" fn pthread_join(thread: pthread_t, arg_return: ?&?&c_void) c_int; | ||
| 64 | |||
| 65 | pub const pthread_t = &@OpaqueType(); |
std/c/linux.zig+5| ... | @@ -3,3 +3,8 @@ pub use @import("../os/linux/errno.zig"); | ... | @@ -3,3 +3,8 @@ pub use @import("../os/linux/errno.zig"); |
| 3 | pub extern "c" fn getrandom(buf_ptr: &u8, buf_len: usize, flags: c_uint) c_int; | 3 | pub extern "c" fn getrandom(buf_ptr: &u8, buf_len: usize, flags: c_uint) c_int; |
| 4 | extern "c" fn __errno_location() &c_int; | 4 | extern "c" fn __errno_location() &c_int; |
| 5 | pub const _errno = __errno_location; | 5 | pub const _errno = __errno_location; |
| 6 | |||
| 7 | pub const pthread_attr_t = extern struct { | ||
| 8 | __size: [56]u8, | ||
| 9 | __align: c_long, | ||
| 10 | }; |
std/heap.zig+59-11| ... | @@ -47,13 +47,6 @@ pub const DirectAllocator = struct { | ... | @@ -47,13 +47,6 @@ pub const DirectAllocator = struct { |
| 47 | 47 | ||
| 48 | const HeapHandle = if (builtin.os == Os.windows) os.windows.HANDLE else void; | 48 | const HeapHandle = if (builtin.os == Os.windows) os.windows.HANDLE else void; |
| 49 | 49 | ||
| 50 | //pub const canary_bytes = []u8 {48, 239, 128, 46, 18, 49, 147, 9, 195, 59, 203, 3, 245, 54, 9, 122}; | ||
| 51 | //pub const want_safety = switch (builtin.mode) { | ||
| 52 | // builtin.Mode.Debug => true, | ||
| 53 | // builtin.Mode.ReleaseSafe => true, | ||
| 54 | // else => false, | ||
| 55 | //}; | ||
| 56 | |||
| 57 | pub fn init() DirectAllocator { | 50 | pub fn init() DirectAllocator { |
| 58 | return DirectAllocator { | 51 | return DirectAllocator { |
| 59 | .allocator = Allocator { | 52 | .allocator = Allocator { |
| ... | @@ -98,7 +91,7 @@ pub const DirectAllocator = struct { | ... | @@ -98,7 +91,7 @@ pub const DirectAllocator = struct { |
| 98 | const unused_start = addr; | 91 | const unused_start = addr; |
| 99 | const unused_len = aligned_addr - 1 - unused_start; | 92 | const unused_len = aligned_addr - 1 - unused_start; |
| 100 | 93 | ||
| 101 | var err = p.munmap(@intToPtr(&u8, unused_start), unused_len); | 94 | var err = p.munmap(unused_start, unused_len); |
| 102 | debug.assert(p.getErrno(err) == 0); | 95 | debug.assert(p.getErrno(err) == 0); |
| 103 | 96 | ||
| 104 | //It is impossible that there is an unoccupied page at the top of our | 97 | //It is impossible that there is an unoccupied page at the top of our |
| ... | @@ -139,7 +132,7 @@ pub const DirectAllocator = struct { | ... | @@ -139,7 +132,7 @@ pub const DirectAllocator = struct { |
| 139 | const rem = @rem(new_addr_end, os.page_size); | 132 | const rem = @rem(new_addr_end, os.page_size); |
| 140 | const new_addr_end_rounded = new_addr_end + if (rem == 0) 0 else (os.page_size - rem); | 133 | const new_addr_end_rounded = new_addr_end + if (rem == 0) 0 else (os.page_size - rem); |
| 141 | if (old_addr_end > new_addr_end_rounded) { | 134 | if (old_addr_end > new_addr_end_rounded) { |
| 142 | _ = os.posix.munmap(@intToPtr(&u8, new_addr_end_rounded), old_addr_end - new_addr_end_rounded); | 135 | _ = os.posix.munmap(new_addr_end_rounded, old_addr_end - new_addr_end_rounded); |
| 143 | } | 136 | } |
| 144 | return old_mem[0..new_size]; | 137 | return old_mem[0..new_size]; |
| 145 | } | 138 | } |
| ... | @@ -177,7 +170,7 @@ pub const DirectAllocator = struct { | ... | @@ -177,7 +170,7 @@ pub const DirectAllocator = struct { |
| 177 | 170 | ||
| 178 | switch (builtin.os) { | 171 | switch (builtin.os) { |
| 179 | Os.linux, Os.macosx, Os.ios => { | 172 | Os.linux, Os.macosx, Os.ios => { |
| 180 | _ = os.posix.munmap(bytes.ptr, bytes.len); | 173 | _ = os.posix.munmap(@ptrToInt(bytes.ptr), bytes.len); |
| 181 | }, | 174 | }, |
| 182 | Os.windows => { | 175 | Os.windows => { |
| 183 | const record_addr = @ptrToInt(bytes.ptr) + bytes.len; | 176 | const record_addr = @ptrToInt(bytes.ptr) + bytes.len; |
| ... | @@ -298,7 +291,7 @@ pub const FixedBufferAllocator = struct { | ... | @@ -298,7 +291,7 @@ pub const FixedBufferAllocator = struct { |
| 298 | 291 | ||
| 299 | fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 { | 292 | fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 { |
| 300 | const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); | 293 | const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); |
| 301 | const addr = @ptrToInt(&self.buffer[self.end_index]); | 294 | const addr = @ptrToInt(self.buffer.ptr) + self.end_index; |
| 302 | const rem = @rem(addr, alignment); | 295 | const rem = @rem(addr, alignment); |
| 303 | const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); | 296 | const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); |
| 304 | const adjusted_index = self.end_index + march_forward_bytes; | 297 | const adjusted_index = self.end_index + march_forward_bytes; |
| ... | @@ -325,6 +318,54 @@ pub const FixedBufferAllocator = struct { | ... | @@ -325,6 +318,54 @@ pub const FixedBufferAllocator = struct { |
| 325 | fn free(allocator: &Allocator, bytes: []u8) void { } | 318 | fn free(allocator: &Allocator, bytes: []u8) void { } |
| 326 | }; | 319 | }; |
| 327 | 320 | ||
| 321 | /// lock free | ||
| 322 | pub const ThreadSafeFixedBufferAllocator = struct { | ||
| 323 | allocator: Allocator, | ||
| 324 | end_index: usize, | ||
| 325 | buffer: []u8, | ||
| 326 | |||
| 327 | pub fn init(buffer: []u8) ThreadSafeFixedBufferAllocator { | ||
| 328 | return ThreadSafeFixedBufferAllocator { | ||
| 329 | .allocator = Allocator { | ||
| 330 | .allocFn = alloc, | ||
| 331 | .reallocFn = realloc, | ||
| 332 | .freeFn = free, | ||
| 333 | }, | ||
| 334 | .buffer = buffer, | ||
| 335 | .end_index = 0, | ||
| 336 | }; | ||
| 337 | } | ||
| 338 | |||
| 339 | fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 { | ||
| 340 | const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator); | ||
| 341 | var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst); | ||
| 342 | while (true) { | ||
| 343 | const addr = @ptrToInt(self.buffer.ptr) + end_index; | ||
| 344 | const rem = @rem(addr, alignment); | ||
| 345 | const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); | ||
| 346 | const adjusted_index = end_index + march_forward_bytes; | ||
| 347 | const new_end_index = adjusted_index + n; | ||
| 348 | if (new_end_index > self.buffer.len) { | ||
| 349 | return error.OutOfMemory; | ||
| 350 | } | ||
| 351 | end_index = @cmpxchgWeak(usize, &self.end_index, end_index, new_end_index, | ||
| 352 | builtin.AtomicOrder.SeqCst, builtin.AtomicOrder.SeqCst) ?? return self.buffer[adjusted_index .. new_end_index]; | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 { | ||
| 357 | if (new_size <= old_mem.len) { | ||
| 358 | return old_mem[0..new_size]; | ||
| 359 | } else { | ||
| 360 | const result = try alloc(allocator, new_size, alignment); | ||
| 361 | mem.copy(u8, result, old_mem); | ||
| 362 | return result; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 366 | fn free(allocator: &Allocator, bytes: []u8) void { } | ||
| 367 | }; | ||
| 368 | |||
| 328 | 369 | ||
| 329 | 370 | ||
| 330 | test "c_allocator" { | 371 | test "c_allocator" { |
| ... | @@ -363,6 +404,13 @@ test "FixedBufferAllocator" { | ... | @@ -363,6 +404,13 @@ test "FixedBufferAllocator" { |
| 363 | try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); | 404 | try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); |
| 364 | } | 405 | } |
| 365 | 406 | ||
| 407 | test "ThreadSafeFixedBufferAllocator" { | ||
| 408 | var fixed_buffer_allocator = ThreadSafeFixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]); | ||
| 409 | |||
| 410 | try testAllocator(&fixed_buffer_allocator.allocator); | ||
| 411 | try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); | ||
| 412 | } | ||
| 413 | |||
| 366 | fn testAllocator(allocator: &mem.Allocator) !void { | 414 | fn testAllocator(allocator: &mem.Allocator) !void { |
| 367 | var slice = try allocator.alloc(&i32, 100); | 415 | var slice = try allocator.alloc(&i32, 100); |
| 368 | 416 |
std/index.zig+2| ... | @@ -8,6 +8,7 @@ pub const HashMap = @import("hash_map.zig").HashMap; | ... | @@ -8,6 +8,7 @@ pub const HashMap = @import("hash_map.zig").HashMap; |
| 8 | pub const LinkedList = @import("linked_list.zig").LinkedList; | 8 | pub const LinkedList = @import("linked_list.zig").LinkedList; |
| 9 | pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList; | 9 | pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList; |
| 10 | 10 | ||
| 11 | pub const atomic = @import("atomic/index.zig"); | ||
| 11 | pub const base64 = @import("base64.zig"); | 12 | pub const base64 = @import("base64.zig"); |
| 12 | pub const build = @import("build.zig"); | 13 | pub const build = @import("build.zig"); |
| 13 | pub const c = @import("c/index.zig"); | 14 | pub const c = @import("c/index.zig"); |
| ... | @@ -34,6 +35,7 @@ pub const zig = @import("zig/index.zig"); | ... | @@ -34,6 +35,7 @@ pub const zig = @import("zig/index.zig"); |
| 34 | 35 | ||
| 35 | test "std" { | 36 | test "std" { |
| 36 | // run tests from these | 37 | // run tests from these |
| 38 | _ = @import("atomic/index.zig"); | ||
| 37 | _ = @import("array_list.zig"); | 39 | _ = @import("array_list.zig"); |
| 38 | _ = @import("buf_map.zig"); | 40 | _ = @import("buf_map.zig"); |
| 39 | _ = @import("buf_set.zig"); | 41 | _ = @import("buf_set.zig"); |
std/mem.zig+1| ... | @@ -32,6 +32,7 @@ pub const Allocator = struct { | ... | @@ -32,6 +32,7 @@ pub const Allocator = struct { |
| 32 | freeFn: fn (self: &Allocator, old_mem: []u8) void, | 32 | freeFn: fn (self: &Allocator, old_mem: []u8) void, |
| 33 | 33 | ||
| 34 | fn create(self: &Allocator, comptime T: type) !&T { | 34 | fn create(self: &Allocator, comptime T: type) !&T { |
| 35 | if (@sizeOf(T) == 0) return &{}; | ||
| 35 | const slice = try self.alloc(T, 1); | 36 | const slice = try self.alloc(T, 1); |
| 36 | return &slice[0]; | 37 | return &slice[0]; |
| 37 | } | 38 | } |
std/os/darwin.zig+4-4| ... | @@ -184,7 +184,7 @@ pub fn write(fd: i32, buf: &const u8, nbyte: usize) usize { | ... | @@ -184,7 +184,7 @@ pub fn write(fd: i32, buf: &const u8, nbyte: usize) usize { |
| 184 | return errnoWrap(c.write(fd, @ptrCast(&const c_void, buf), nbyte)); | 184 | return errnoWrap(c.write(fd, @ptrCast(&const c_void, buf), nbyte)); |
| 185 | } | 185 | } |
| 186 | 186 | ||
| 187 | pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, | 187 | pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: u32, fd: i32, |
| 188 | offset: isize) usize | 188 | offset: isize) usize |
| 189 | { | 189 | { |
| 190 | const ptr_result = c.mmap(@ptrCast(&c_void, address), length, | 190 | const ptr_result = c.mmap(@ptrCast(&c_void, address), length, |
| ... | @@ -193,8 +193,8 @@ pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, | ... | @@ -193,8 +193,8 @@ pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, |
| 193 | return errnoWrap(isize_result); | 193 | return errnoWrap(isize_result); |
| 194 | } | 194 | } |
| 195 | 195 | ||
| 196 | pub fn munmap(address: &u8, length: usize) usize { | 196 | pub fn munmap(address: usize, length: usize) usize { |
| 197 | return errnoWrap(c.munmap(@ptrCast(&c_void, address), length)); | 197 | return errnoWrap(c.munmap(@intToPtr(&c_void, address), length)); |
| 198 | } | 198 | } |
| 199 | 199 | ||
| 200 | pub fn unlink(path: &const u8) usize { | 200 | pub fn unlink(path: &const u8) usize { |
| ... | @@ -341,4 +341,4 @@ pub const timeval = c.timeval; | ... | @@ -341,4 +341,4 @@ pub const timeval = c.timeval; |
| 341 | pub const mach_timebase_info_data = c.mach_timebase_info_data; | 341 | pub const mach_timebase_info_data = c.mach_timebase_info_data; |
| 342 | 342 | ||
| 343 | pub const mach_absolute_time = c.mach_absolute_time; | 343 | pub const mach_absolute_time = c.mach_absolute_time; |
| 344 | pub const mach_timebase_info = c.mach_timebase_info; | ||
| \ No newline at end of file | |||
| 344 | pub const mach_timebase_info = c.mach_timebase_info; | ||
std/os/index.zig+167-56| ... | @@ -2,6 +2,10 @@ const std = @import("../index.zig"); | ... | @@ -2,6 +2,10 @@ const std = @import("../index.zig"); |
| 2 | const builtin = @import("builtin"); | 2 | const builtin = @import("builtin"); |
| 3 | const Os = builtin.Os; | 3 | const Os = builtin.Os; |
| 4 | const is_windows = builtin.os == Os.windows; | 4 | const is_windows = builtin.os == Os.windows; |
| 5 | const is_posix = switch (builtin.os) { | ||
| 6 | builtin.Os.linux, builtin.Os.macosx => true, | ||
| 7 | else => false, | ||
| 8 | }; | ||
| 5 | const os = this; | 9 | const os = this; |
| 6 | 10 | ||
| 7 | test "std.os" { | 11 | test "std.os" { |
| ... | @@ -2343,24 +2347,58 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { | ... | @@ -2343,24 +2347,58 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { |
| 2343 | } | 2347 | } |
| 2344 | 2348 | ||
| 2345 | pub const Thread = struct { | 2349 | pub const Thread = struct { |
| 2346 | pid: i32, | 2350 | data: Data, |
| 2347 | allocator: ?&mem.Allocator, | 2351 | |
| 2348 | stack: []u8, | 2352 | pub const use_pthreads = is_posix and builtin.link_libc; |
| 2353 | const Data = if (use_pthreads) struct { | ||
| 2354 | handle: c.pthread_t, | ||
| 2355 | stack_addr: usize, | ||
| 2356 | stack_len: usize, | ||
| 2357 | } else switch (builtin.os) { | ||
| 2358 | builtin.Os.linux => struct { | ||
| 2359 | pid: i32, | ||
| 2360 | stack_addr: usize, | ||
| 2361 | stack_len: usize, | ||
| 2362 | }, | ||
| 2363 | builtin.Os.windows => struct { | ||
| 2364 | handle: windows.HANDLE, | ||
| 2365 | alloc_start: &c_void, | ||
| 2366 | heap_handle: windows.HANDLE, | ||
| 2367 | }, | ||
| 2368 | else => @compileError("Unsupported OS"), | ||
| 2369 | }; | ||
| 2349 | 2370 | ||
| 2350 | pub fn wait(self: &const Thread) void { | 2371 | pub fn wait(self: &const Thread) void { |
| 2351 | while (true) { | 2372 | if (use_pthreads) { |
| 2352 | const pid_value = @atomicLoad(i32, &self.pid, builtin.AtomicOrder.SeqCst); | 2373 | const err = c.pthread_join(self.data.handle, null); |
| 2353 | if (pid_value == 0) break; | 2374 | switch (err) { |
| 2354 | const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null); | 2375 | 0 => {}, |
| 2355 | switch (linux.getErrno(rc)) { | 2376 | posix.EINVAL => unreachable, |
| 2356 | 0 => continue, | 2377 | posix.ESRCH => unreachable, |
| 2357 | posix.EINTR => continue, | 2378 | posix.EDEADLK => unreachable, |
| 2358 | posix.EAGAIN => continue, | ||
| 2359 | else => unreachable, | 2379 | else => unreachable, |
| 2360 | } | 2380 | } |
| 2361 | } | 2381 | assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0); |
| 2362 | if (self.allocator) |a| { | 2382 | } else switch (builtin.os) { |
| 2363 | a.free(self.stack); | 2383 | builtin.Os.linux => { |
| 2384 | while (true) { | ||
| 2385 | const pid_value = @atomicLoad(i32, &self.data.pid, builtin.AtomicOrder.SeqCst); | ||
| 2386 | if (pid_value == 0) break; | ||
| 2387 | const rc = linux.futex_wait(@ptrToInt(&self.data.pid), linux.FUTEX_WAIT, pid_value, null); | ||
| 2388 | switch (linux.getErrno(rc)) { | ||
| 2389 | 0 => continue, | ||
| 2390 | posix.EINTR => continue, | ||
| 2391 | posix.EAGAIN => continue, | ||
| 2392 | else => unreachable, | ||
| 2393 | } | ||
| 2394 | } | ||
| 2395 | assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0); | ||
| 2396 | }, | ||
| 2397 | builtin.Os.windows => { | ||
| 2398 | assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0); | ||
| 2399 | assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0); | ||
| 2400 | }, | ||
| 2401 | else => @compileError("Unsupported OS"), | ||
| 2364 | } | 2402 | } |
| 2365 | } | 2403 | } |
| 2366 | }; | 2404 | }; |
| ... | @@ -2385,38 +2423,94 @@ pub const SpawnThreadError = error { | ... | @@ -2385,38 +2423,94 @@ pub const SpawnThreadError = error { |
| 2385 | /// be copied. | 2423 | /// be copied. |
| 2386 | SystemResources, | 2424 | SystemResources, |
| 2387 | 2425 | ||
| 2426 | /// Not enough userland memory to spawn the thread. | ||
| 2427 | OutOfMemory, | ||
| 2428 | |||
| 2388 | Unexpected, | 2429 | Unexpected, |
| 2389 | }; | 2430 | }; |
| 2390 | 2431 | ||
| 2391 | pub const SpawnThreadAllocatorError = SpawnThreadError || error{OutOfMemory}; | ||
| 2392 | |||
| 2393 | /// caller must call wait on the returned thread | 2432 | /// caller must call wait on the returned thread |
| 2394 | /// fn startFn(@typeOf(context)) T | 2433 | /// fn startFn(@typeOf(context)) T |
| 2395 | /// where T is u8, noreturn, void, or !void | 2434 | /// where T is u8, noreturn, void, or !void |
| 2396 | pub fn spawnThreadAllocator(allocator: &mem.Allocator, context: var, comptime startFn: var) SpawnThreadAllocatorError!&Thread { | 2435 | /// caller must call wait on the returned thread |
| 2436 | pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread { | ||
| 2397 | // TODO compile-time call graph analysis to determine stack upper bound | 2437 | // TODO compile-time call graph analysis to determine stack upper bound |
| 2398 | // https://github.com/zig-lang/zig/issues/157 | 2438 | // https://github.com/zig-lang/zig/issues/157 |
| 2399 | const default_stack_size = 8 * 1024 * 1024; | 2439 | const default_stack_size = 8 * 1024 * 1024; |
| 2400 | const stack_bytes = try allocator.alloc(u8, default_stack_size); | ||
| 2401 | const thread = try spawnThread(stack_bytes, context, startFn); | ||
| 2402 | thread.allocator = allocator; | ||
| 2403 | return thread; | ||
| 2404 | } | ||
| 2405 | 2440 | ||
| 2406 | /// stack must be big enough to store one Thread and one @typeOf(context), each with default alignment, at the end | ||
| 2407 | /// fn startFn(@typeOf(context)) T | ||
| 2408 | /// where T is u8, noreturn, void, or !void | ||
| 2409 | /// caller must call wait on the returned thread | ||
| 2410 | pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThreadError!&Thread { | ||
| 2411 | const Context = @typeOf(context); | 2441 | const Context = @typeOf(context); |
| 2412 | comptime assert(@ArgType(@typeOf(startFn), 0) == Context); | 2442 | comptime assert(@ArgType(@typeOf(startFn), 0) == Context); |
| 2413 | 2443 | ||
| 2414 | var stack_end: usize = @ptrToInt(stack.ptr) + stack.len; | 2444 | if (builtin.os == builtin.Os.windows) { |
| 2445 | const WinThread = struct { | ||
| 2446 | const OuterContext = struct { | ||
| 2447 | thread: Thread, | ||
| 2448 | inner: Context, | ||
| 2449 | }; | ||
| 2450 | extern fn threadMain(arg: windows.LPVOID) windows.DWORD { | ||
| 2451 | if (@sizeOf(Context) == 0) { | ||
| 2452 | return startFn({}); | ||
| 2453 | } else { | ||
| 2454 | return startFn(*@ptrCast(&Context, @alignCast(@alignOf(Context), arg))); | ||
| 2455 | } | ||
| 2456 | } | ||
| 2457 | }; | ||
| 2458 | |||
| 2459 | const heap_handle = windows.GetProcessHeap() ?? return SpawnThreadError.OutOfMemory; | ||
| 2460 | const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); | ||
| 2461 | const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) ?? return SpawnThreadError.OutOfMemory; | ||
| 2462 | errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0); | ||
| 2463 | const bytes = @ptrCast(&u8, bytes_ptr)[0..byte_count]; | ||
| 2464 | const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; | ||
| 2465 | outer_context.inner = context; | ||
| 2466 | outer_context.thread.data.heap_handle = heap_handle; | ||
| 2467 | outer_context.thread.data.alloc_start = bytes_ptr; | ||
| 2468 | |||
| 2469 | const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(&c_void, &outer_context.inner); | ||
| 2470 | outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, | ||
| 2471 | parameter, 0, null) ?? | ||
| 2472 | { | ||
| 2473 | const err = windows.GetLastError(); | ||
| 2474 | return switch (err) { | ||
| 2475 | else => os.unexpectedErrorWindows(err), | ||
| 2476 | }; | ||
| 2477 | }; | ||
| 2478 | return &outer_context.thread; | ||
| 2479 | } | ||
| 2480 | |||
| 2481 | const MainFuncs = struct { | ||
| 2482 | extern fn linuxThreadMain(ctx_addr: usize) u8 { | ||
| 2483 | if (@sizeOf(Context) == 0) { | ||
| 2484 | return startFn({}); | ||
| 2485 | } else { | ||
| 2486 | return startFn(*@intToPtr(&const Context, ctx_addr)); | ||
| 2487 | } | ||
| 2488 | } | ||
| 2489 | extern fn posixThreadMain(ctx: ?&c_void) ?&c_void { | ||
| 2490 | if (@sizeOf(Context) == 0) { | ||
| 2491 | _ = startFn({}); | ||
| 2492 | return null; | ||
| 2493 | } else { | ||
| 2494 | _ = startFn(*@ptrCast(&const Context, @alignCast(@alignOf(Context), ctx))); | ||
| 2495 | return null; | ||
| 2496 | } | ||
| 2497 | } | ||
| 2498 | }; | ||
| 2499 | |||
| 2500 | const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; | ||
| 2501 | |||
| 2502 | const mmap_len = default_stack_size; | ||
| 2503 | const stack_addr = posix.mmap(null, mmap_len, posix.PROT_READ|posix.PROT_WRITE, | ||
| 2504 | posix.MAP_PRIVATE|posix.MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0); | ||
| 2505 | if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory; | ||
| 2506 | errdefer assert(posix.munmap(stack_addr, mmap_len) == 0); | ||
| 2507 | |||
| 2508 | var stack_end: usize = stack_addr + mmap_len; | ||
| 2415 | var arg: usize = undefined; | 2509 | var arg: usize = undefined; |
| 2416 | if (@sizeOf(Context) != 0) { | 2510 | if (@sizeOf(Context) != 0) { |
| 2417 | stack_end -= @sizeOf(Context); | 2511 | stack_end -= @sizeOf(Context); |
| 2418 | stack_end -= stack_end % @alignOf(Context); | 2512 | stack_end -= stack_end % @alignOf(Context); |
| 2419 | assert(stack_end >= @ptrToInt(stack.ptr)); | 2513 | assert(stack_end >= stack_addr); |
| 2420 | const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end)); | 2514 | const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end)); |
| 2421 | *context_ptr = context; | 2515 | *context_ptr = context; |
| 2422 | arg = stack_end; | 2516 | arg = stack_end; |
| ... | @@ -2424,36 +2518,53 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread | ... | @@ -2424,36 +2518,53 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread |
| 2424 | 2518 | ||
| 2425 | stack_end -= @sizeOf(Thread); | 2519 | stack_end -= @sizeOf(Thread); |
| 2426 | stack_end -= stack_end % @alignOf(Thread); | 2520 | stack_end -= stack_end % @alignOf(Thread); |
| 2427 | assert(stack_end >= @ptrToInt(stack.ptr)); | 2521 | assert(stack_end >= stack_addr); |
| 2428 | const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end)); | 2522 | const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end)); |
| 2429 | thread_ptr.stack = stack; | ||
| 2430 | thread_ptr.allocator = null; | ||
| 2431 | 2523 | ||
| 2432 | const threadMain = struct { | 2524 | thread_ptr.data.stack_addr = stack_addr; |
| 2433 | extern fn threadMain(ctx_addr: usize) u8 { | 2525 | thread_ptr.data.stack_len = mmap_len; |
| 2434 | if (@sizeOf(Context) == 0) { | ||
| 2435 | return startFn({}); | ||
| 2436 | } else { | ||
| 2437 | return startFn(*@intToPtr(&const Context, ctx_addr)); | ||
| 2438 | } | ||
| 2439 | } | ||
| 2440 | }.threadMain; | ||
| 2441 | 2526 | ||
| 2442 | const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | 2527 | if (builtin.os == builtin.Os.windows) { |
| 2443 | | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS | 2528 | // use windows API directly |
| 2444 | | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; | 2529 | @compileError("TODO support spawnThread for Windows"); |
| 2445 | const newtls: usize = 0; | 2530 | } else if (Thread.use_pthreads) { |
| 2446 | const rc = posix.clone(threadMain, stack_end, flags, arg, &thread_ptr.pid, newtls, &thread_ptr.pid); | 2531 | // use pthreads |
| 2447 | const err = posix.getErrno(rc); | 2532 | var attr: c.pthread_attr_t = undefined; |
| 2448 | switch (err) { | 2533 | if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; |
| 2449 | 0 => return thread_ptr, | 2534 | defer assert(c.pthread_attr_destroy(&attr) == 0); |
| 2450 | posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded, | 2535 | |
| 2451 | posix.EINVAL => unreachable, | 2536 | // align to page |
| 2452 | posix.ENOMEM => return SpawnThreadError.SystemResources, | 2537 | stack_end -= stack_end % os.page_size; |
| 2453 | posix.ENOSPC => unreachable, | 2538 | assert(c.pthread_attr_setstack(&attr, @intToPtr(&c_void, stack_addr), stack_end - stack_addr) == 0); |
| 2454 | posix.EPERM => unreachable, | 2539 | |
| 2455 | posix.EUSERS => unreachable, | 2540 | const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg)); |
| 2456 | else => return unexpectedErrorPosix(err), | 2541 | switch (err) { |
| 2542 | 0 => return thread_ptr, | ||
| 2543 | posix.EAGAIN => return SpawnThreadError.SystemResources, | ||
| 2544 | posix.EPERM => unreachable, | ||
| 2545 | posix.EINVAL => unreachable, | ||
| 2546 | else => return unexpectedErrorPosix(usize(err)), | ||
| 2547 | } | ||
| 2548 | } else if (builtin.os == builtin.Os.linux) { | ||
| 2549 | // use linux API directly | ||
| 2550 | const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | ||
| 2551 | | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS | ||
| 2552 | | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; | ||
| 2553 | const newtls: usize = 0; | ||
| 2554 | const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.pid, newtls, &thread_ptr.data.pid); | ||
| 2555 | const err = posix.getErrno(rc); | ||
| 2556 | switch (err) { | ||
| 2557 | 0 => return thread_ptr, | ||
| 2558 | posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded, | ||
| 2559 | posix.EINVAL => unreachable, | ||
| 2560 | posix.ENOMEM => return SpawnThreadError.SystemResources, | ||
| 2561 | posix.ENOSPC => unreachable, | ||
| 2562 | posix.EPERM => unreachable, | ||
| 2563 | posix.EUSERS => unreachable, | ||
| 2564 | else => return unexpectedErrorPosix(err), | ||
| 2565 | } | ||
| 2566 | } else { | ||
| 2567 | @compileError("Unsupported OS"); | ||
| 2457 | } | 2568 | } |
| 2458 | } | 2569 | } |
| 2459 | 2570 |
std/os/linux/index.zig+3-3| ... | @@ -706,13 +706,13 @@ pub fn umount2(special: &const u8, flags: u32) usize { | ... | @@ -706,13 +706,13 @@ pub fn umount2(special: &const u8, flags: u32) usize { |
| 706 | return syscall2(SYS_umount2, @ptrToInt(special), flags); | 706 | return syscall2(SYS_umount2, @ptrToInt(special), flags); |
| 707 | } | 707 | } |
| 708 | 708 | ||
| 709 | pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: isize) usize { | 709 | pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: u32, fd: i32, offset: isize) usize { |
| 710 | return syscall6(SYS_mmap, @ptrToInt(address), length, prot, flags, usize(fd), | 710 | return syscall6(SYS_mmap, @ptrToInt(address), length, prot, flags, usize(fd), |
| 711 | @bitCast(usize, offset)); | 711 | @bitCast(usize, offset)); |
| 712 | } | 712 | } |
| 713 | 713 | ||
| 714 | pub fn munmap(address: &u8, length: usize) usize { | 714 | pub fn munmap(address: usize, length: usize) usize { |
| 715 | return syscall2(SYS_munmap, @ptrToInt(address), length); | 715 | return syscall2(SYS_munmap, address, length); |
| 716 | } | 716 | } |
| 717 | 717 | ||
| 718 | pub fn read(fd: i32, buf: &u8, count: usize) usize { | 718 | pub fn read(fd: i32, buf: &u8, count: usize) usize { |
std/os/test.zig+4-16| ... | @@ -44,24 +44,12 @@ test "access file" { | ... | @@ -44,24 +44,12 @@ test "access file" { |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | test "spawn threads" { | 46 | test "spawn threads" { |
| 47 | if (builtin.os != builtin.Os.linux) { | ||
| 48 | // TODO implement threads on macos and windows | ||
| 49 | return; | ||
| 50 | } | ||
| 51 | |||
| 52 | var direct_allocator = std.heap.DirectAllocator.init(); | ||
| 53 | defer direct_allocator.deinit(); | ||
| 54 | |||
| 55 | var shared_ctx: i32 = 1; | 47 | var shared_ctx: i32 = 1; |
| 56 | 48 | ||
| 57 | const thread1 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, {}, start1); | 49 | const thread1 = try std.os.spawnThread({}, start1); |
| 58 | const thread4 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, &shared_ctx, start2); | 50 | const thread2 = try std.os.spawnThread(&shared_ctx, start2); |
| 59 | 51 | const thread3 = try std.os.spawnThread(&shared_ctx, start2); | |
| 60 | var stack1: [1024]u8 = undefined; | 52 | const thread4 = try std.os.spawnThread(&shared_ctx, start2); |
| 61 | var stack2: [1024]u8 = undefined; | ||
| 62 | |||
| 63 | const thread2 = try std.os.spawnThread(stack1[0..], &shared_ctx, start2); | ||
| 64 | const thread3 = try std.os.spawnThread(stack2[0..], &shared_ctx, start2); | ||
| 65 | 53 | ||
| 66 | thread1.wait(); | 54 | thread1.wait(); |
| 67 | thread2.wait(); | 55 | thread2.wait(); |
std/os/windows/index.zig+6| ... | @@ -28,6 +28,9 @@ pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lp | ... | @@ -28,6 +28,9 @@ pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lp |
| 28 | pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(lpSymlinkFileName: LPCSTR, lpTargetFileName: LPCSTR, | 28 | pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(lpSymlinkFileName: LPCSTR, lpTargetFileName: LPCSTR, |
| 29 | dwFlags: DWORD) BOOLEAN; | 29 | dwFlags: DWORD) BOOLEAN; |
| 30 | 30 | ||
| 31 | |||
| 32 | pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE; | ||
| 33 | |||
| 31 | pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL; | 34 | pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL; |
| 32 | 35 | ||
| 33 | pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; | 36 | pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; |
| ... | @@ -318,6 +321,9 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000; | ... | @@ -318,6 +321,9 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000; |
| 318 | pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004; | 321 | pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004; |
| 319 | pub const HEAP_NO_SERIALIZE = 0x00000001; | 322 | pub const HEAP_NO_SERIALIZE = 0x00000001; |
| 320 | 323 | ||
| 324 | pub const PTHREAD_START_ROUTINE = extern fn(LPVOID) DWORD; | ||
| 325 | pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE; | ||
| 326 | |||
| 321 | test "import" { | 327 | test "import" { |
| 322 | _ = @import("util.zig"); | 328 | _ = @import("util.zig"); |
| 323 | } | 329 | } |