Skip to content

Commit ab4eeb7

Browse files
authored
Merge pull request #20528 from jacobly0/tsip
InternPool: begin conversion to thread-safe data structure
2 parents 8f20e81 + 65ced4a commit ab4eeb7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+15011
-12550
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ set(ZIG_STAGE2_SOURCES
525525
src/Type.zig
526526
src/Value.zig
527527
src/Zcu.zig
528+
src/Zcu/PerThread.zig
528529
src/arch/aarch64/CodeGen.zig
529530
src/arch/aarch64/Emit.zig
530531
src/arch/aarch64/Mir.zig

lib/std/Progress.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ pub const Node = struct {
282282
}
283283

284284
fn init(free_index: Index, parent: Parent, name: []const u8, estimated_total_items: usize) Node {
285-
assert(parent != .unused);
285+
assert(parent == .none or @intFromEnum(parent) < node_storage_buffer_len);
286286

287287
const storage = storageByIndex(free_index);
288288
storage.* = .{

lib/std/Thread.zig

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,13 @@ pub fn getCurrentId() Id {
280280
pub const CpuCountError = error{
281281
PermissionDenied,
282282
SystemResources,
283+
Unsupported,
283284
Unexpected,
284285
};
285286

286287
/// Returns the platforms view on the number of logical CPU cores available.
287288
pub fn getCpuCount() CpuCountError!usize {
288-
return Impl.getCpuCount();
289+
return try Impl.getCpuCount();
289290
}
290291

291292
/// Configuration options for hints on how to spawn threads.
@@ -782,6 +783,10 @@ const WasiThreadImpl = struct {
782783
return tls_thread_id;
783784
}
784785

786+
fn getCpuCount() error{Unsupported}!noreturn {
787+
return error.Unsupported;
788+
}
789+
785790
fn getHandle(self: Impl) ThreadHandle {
786791
return self.thread.tid.load(.seq_cst);
787792
}

lib/std/Thread/Pool.zig

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,45 @@ cond: std.Thread.Condition = .{},
88
run_queue: RunQueue = .{},
99
is_running: bool = true,
1010
allocator: std.mem.Allocator,
11-
threads: []std.Thread,
11+
threads: if (builtin.single_threaded) [0]std.Thread else []std.Thread,
12+
ids: if (builtin.single_threaded) struct {
13+
inline fn deinit(_: @This(), _: std.mem.Allocator) void {}
14+
fn getIndex(_: @This(), _: std.Thread.Id) usize {
15+
return 0;
16+
}
17+
} else std.AutoArrayHashMapUnmanaged(std.Thread.Id, void),
1218

1319
const RunQueue = std.SinglyLinkedList(Runnable);
1420
const Runnable = struct {
1521
runFn: RunProto,
1622
};
1723

18-
const RunProto = *const fn (*Runnable) void;
24+
const RunProto = *const fn (*Runnable, id: ?usize) void;
1925

2026
pub const Options = struct {
2127
allocator: std.mem.Allocator,
22-
n_jobs: ?u32 = null,
28+
n_jobs: ?usize = null,
29+
track_ids: bool = false,
2330
};
2431

2532
pub fn init(pool: *Pool, options: Options) !void {
2633
const allocator = options.allocator;
2734

2835
pool.* = .{
2936
.allocator = allocator,
30-
.threads = &[_]std.Thread{},
37+
.threads = if (builtin.single_threaded) .{} else &.{},
38+
.ids = .{},
3139
};
3240

3341
if (builtin.single_threaded) {
3442
return;
3543
}
3644

3745
const thread_count = options.n_jobs orelse @max(1, std.Thread.getCpuCount() catch 1);
46+
if (options.track_ids) {
47+
try pool.ids.ensureTotalCapacity(allocator, 1 + thread_count);
48+
pool.ids.putAssumeCapacityNoClobber(std.Thread.getCurrentId(), {});
49+
}
3850

3951
// kill and join any threads we spawned and free memory on error.
4052
pool.threads = try allocator.alloc(std.Thread, thread_count);
@@ -49,6 +61,7 @@ pub fn init(pool: *Pool, options: Options) !void {
4961

5062
pub fn deinit(pool: *Pool) void {
5163
pool.join(pool.threads.len); // kill and join all threads.
64+
pool.ids.deinit(pool.allocator);
5265
pool.* = undefined;
5366
}
5467

@@ -96,7 +109,7 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
96109
run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
97110
wait_group: *WaitGroup,
98111

99-
fn runFn(runnable: *Runnable) void {
112+
fn runFn(runnable: *Runnable, _: ?usize) void {
100113
const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
101114
const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
102115
@call(.auto, func, closure.arguments);
@@ -134,6 +147,70 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
134147
pool.cond.signal();
135148
}
136149

150+
/// Runs `func` in the thread pool, calling `WaitGroup.start` beforehand, and
151+
/// `WaitGroup.finish` after it returns.
152+
///
153+
/// The first argument passed to `func` is a dense `usize` thread id, the rest
154+
/// of the arguments are passed from `args`. Requires the pool to have been
155+
/// initialized with `.track_ids = true`.
156+
///
157+
/// In the case that queuing the function call fails to allocate memory, or the
158+
/// target is single-threaded, the function is called directly.
159+
pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args: anytype) void {
160+
wait_group.start();
161+
162+
if (builtin.single_threaded) {
163+
@call(.auto, func, .{0} ++ args);
164+
wait_group.finish();
165+
return;
166+
}
167+
168+
const Args = @TypeOf(args);
169+
const Closure = struct {
170+
arguments: Args,
171+
pool: *Pool,
172+
run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
173+
wait_group: *WaitGroup,
174+
175+
fn runFn(runnable: *Runnable, id: ?usize) void {
176+
const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
177+
const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
178+
@call(.auto, func, .{id.?} ++ closure.arguments);
179+
closure.wait_group.finish();
180+
181+
// The thread pool's allocator is protected by the mutex.
182+
const mutex = &closure.pool.mutex;
183+
mutex.lock();
184+
defer mutex.unlock();
185+
186+
closure.pool.allocator.destroy(closure);
187+
}
188+
};
189+
190+
{
191+
pool.mutex.lock();
192+
193+
const closure = pool.allocator.create(Closure) catch {
194+
const id: ?usize = pool.ids.getIndex(std.Thread.getCurrentId());
195+
pool.mutex.unlock();
196+
@call(.auto, func, .{id.?} ++ args);
197+
wait_group.finish();
198+
return;
199+
};
200+
closure.* = .{
201+
.arguments = args,
202+
.pool = pool,
203+
.wait_group = wait_group,
204+
};
205+
206+
pool.run_queue.prepend(&closure.run_node);
207+
pool.mutex.unlock();
208+
}
209+
210+
// Notify waiting threads outside the lock to try and keep the critical section small.
211+
pool.cond.signal();
212+
}
213+
137214
pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
138215
if (builtin.single_threaded) {
139216
@call(.auto, func, args);
@@ -181,14 +258,16 @@ fn worker(pool: *Pool) void {
181258
pool.mutex.lock();
182259
defer pool.mutex.unlock();
183260

261+
const id: ?usize = if (pool.ids.count() > 0) @intCast(pool.ids.count()) else null;
262+
if (id) |_| pool.ids.putAssumeCapacityNoClobber(std.Thread.getCurrentId(), {});
263+
184264
while (true) {
185265
while (pool.run_queue.popFirst()) |run_node| {
186266
// Temporarily unlock the mutex in order to execute the run_node
187267
pool.mutex.unlock();
188268
defer pool.mutex.lock();
189269

190-
const runFn = run_node.data.runFn;
191-
runFn(&run_node.data);
270+
run_node.data.runFn(&run_node.data, id);
192271
}
193272

194273
// Stop executing instead of waiting if the thread pool is no longer running.
@@ -201,17 +280,23 @@ fn worker(pool: *Pool) void {
201280
}
202281

203282
pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
283+
var id: ?usize = null;
284+
204285
while (!wait_group.isDone()) {
205-
if (blk: {
206-
pool.mutex.lock();
207-
defer pool.mutex.unlock();
208-
break :blk pool.run_queue.popFirst();
209-
}) |run_node| {
210-
run_node.data.runFn(&run_node.data);
286+
pool.mutex.lock();
287+
if (pool.run_queue.popFirst()) |run_node| {
288+
id = id orelse pool.ids.getIndex(std.Thread.getCurrentId());
289+
pool.mutex.unlock();
290+
run_node.data.runFn(&run_node.data, id);
211291
continue;
212292
}
213293

294+
pool.mutex.unlock();
214295
wait_group.wait();
215296
return;
216297
}
217298
}
299+
300+
pub fn getIdCount(pool: *Pool) usize {
301+
return @intCast(1 + pool.threads.len);
302+
}

lib/std/multi_array_list.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ pub fn MultiArrayList(comptime T: type) type {
534534
self.sortInternal(a, b, ctx, .unstable);
535535
}
536536

537-
fn capacityInBytes(capacity: usize) usize {
537+
pub fn capacityInBytes(capacity: usize) usize {
538538
comptime var elem_bytes: usize = 0;
539539
inline for (sizes.bytes) |size| elem_bytes += size;
540540
return elem_bytes * capacity;

src/Air.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,12 +1563,12 @@ pub fn internedToRef(ip_index: InternPool.Index) Inst.Ref {
15631563
}
15641564

15651565
/// Returns `null` if runtime-known.
1566-
pub fn value(air: Air, inst: Inst.Ref, mod: *Module) !?Value {
1566+
pub fn value(air: Air, inst: Inst.Ref, pt: Zcu.PerThread) !?Value {
15671567
if (inst.toInterned()) |ip_index| {
15681568
return Value.fromInterned(ip_index);
15691569
}
15701570
const index = inst.toIndex().?;
1571-
return air.typeOfIndex(index, &mod.intern_pool).onePossibleValue(mod);
1571+
return air.typeOfIndex(index, &pt.zcu.intern_pool).onePossibleValue(pt);
15721572
}
15731573

15741574
pub fn nullTerminatedString(air: Air, index: usize) [:0]const u8 {

0 commit comments

Comments
 (0)