authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-07-08 09:05:30-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-07-08 11:00:38-04:00
log65ced4a33436fa762de75e22a986ae08a8c0d9cc
treed19b6b0cdb71a7e0f6bc65379441992753979a4a
parentc36e2bb9802ab4317980a98ea518483010fe2c80

Compilation: put supported codegen backends on a separate thread

(There are no supported backends.)

7 files changed, 173 insertions(+), 65 deletions(-)

lib/std/Progress.zig+1-1
......@@ -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/Pool.zig+10-13
......@@ -21,11 +21,11 @@ const Runnable = struct {
2121 runFn: RunProto,
2222};
2323
24const RunProto = *const fn (*Runnable, id: ?u32) void;
24const RunProto = *const fn (*Runnable, id: ?usize) void;
2525
2626pub const Options = struct {
2727 allocator: std.mem.Allocator,
28 n_jobs: ?u32 = null,
28 n_jobs: ?usize = null,
2929 track_ids: bool = false,
3030};
3131
......@@ -109,7 +109,7 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
109109 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
110110 wait_group: *WaitGroup,
111111
112 fn runFn(runnable: *Runnable, _: ?u32) void {
112 fn runFn(runnable: *Runnable, _: ?usize) void {
113113 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
114114 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
115115 @call(.auto, func, closure.arguments);
......@@ -150,7 +150,7 @@ pub fn spawnWg(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, args
150150/// Runs `func` in the thread pool, calling `WaitGroup.start` beforehand, and
151151/// `WaitGroup.finish` after it returns.
152152///
153/// The first argument passed to `func` is a dense `u32` thread id, the rest
153/// The first argument passed to `func` is a dense `usize` thread id, the rest
154154/// of the arguments are passed from `args`. Requires the pool to have been
155155/// initialized with `.track_ids = true`.
156156///
......@@ -172,7 +172,7 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
172172 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
173173 wait_group: *WaitGroup,
174174
175 fn runFn(runnable: *Runnable, id: ?u32) void {
175 fn runFn(runnable: *Runnable, id: ?usize) void {
176176 const run_node: *RunQueue.Node = @fieldParentPtr("data", runnable);
177177 const closure: *@This() = @alignCast(@fieldParentPtr("run_node", run_node));
178178 @call(.auto, func, .{id.?} ++ closure.arguments);
......@@ -191,7 +191,7 @@ pub fn spawnWgId(pool: *Pool, wait_group: *WaitGroup, comptime func: anytype, ar
191191 pool.mutex.lock();
192192
193193 const closure = pool.allocator.create(Closure) catch {
194 const id = pool.ids.getIndex(std.Thread.getCurrentId());
194 const id: ?usize = pool.ids.getIndex(std.Thread.getCurrentId());
195195 pool.mutex.unlock();
196196 @call(.auto, func, .{id.?} ++ args);
197197 wait_group.finish();
......@@ -258,7 +258,7 @@ fn worker(pool: *Pool) void {
258258 pool.mutex.lock();
259259 defer pool.mutex.unlock();
260260
261 const id: ?u32 = if (pool.ids.count() > 0) @intCast(pool.ids.count()) else null;
261 const id: ?usize = if (pool.ids.count() > 0) @intCast(pool.ids.count()) else null;
262262 if (id) |_| pool.ids.putAssumeCapacityNoClobber(std.Thread.getCurrentId(), {});
263263
264264 while (true) {
......@@ -280,15 +280,12 @@ fn worker(pool: *Pool) void {
280280}
281281
282282pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
283 var id: ?u32 = null;
283 var id: ?usize = null;
284284
285285 while (!wait_group.isDone()) {
286286 pool.mutex.lock();
287287 if (pool.run_queue.popFirst()) |run_node| {
288 id = id orelse if (pool.ids.getIndex(std.Thread.getCurrentId())) |index|
289 @intCast(index)
290 else
291 null;
288 id = id orelse pool.ids.getIndex(std.Thread.getCurrentId());
292289 pool.mutex.unlock();
293290 run_node.data.runFn(&run_node.data, id);
294291 continue;
......@@ -300,6 +297,6 @@ pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
300297 }
301298}
302299
303pub fn getIdCount(pool: *Pool) u32 {
300pub fn getIdCount(pool: *Pool) usize {
304301 return @intCast(1 + pool.threads.len);
305302}
src/Compilation.zig+115-22
......@@ -103,6 +103,14 @@ lld_errors: std.ArrayListUnmanaged(LldError) = .{},
103103
104104work_queue: std.fifo.LinearFifo(Job, .Dynamic),
105105
106codegen_work: if (InternPool.single_threaded) void else struct {
107 mutex: std.Thread.Mutex,
108 cond: std.Thread.Condition,
109 queue: std.fifo.LinearFifo(CodegenJob, .Dynamic),
110 job_error: ?JobError,
111 done: bool,
112},
113
106114/// These jobs are to invoke the Clang compiler to create an object file, which
107115/// gets linked with the Compilation.
108116c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic),
......@@ -362,6 +370,16 @@ const Job = union(enum) {
362370 windows_import_lib: usize,
363371};
364372
373const CodegenJob = union(enum) {
374 decl: InternPool.DeclIndex,
375 func: struct {
376 func: InternPool.Index,
377 /// This `Air` is owned by the `Job` and allocated with `gpa`.
378 /// It must be deinited when the job is processed.
379 air: Air,
380 },
381};
382
365383pub const CObject = struct {
366384 /// Relative to cwd. Owned by arena.
367385 src: CSourceFile,
......@@ -1429,6 +1447,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
14291447 .emit_llvm_ir = options.emit_llvm_ir,
14301448 .emit_llvm_bc = options.emit_llvm_bc,
14311449 .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
1450 .codegen_work = if (InternPool.single_threaded) {} else .{
1451 .mutex = .{},
1452 .cond = .{},
1453 .queue = std.fifo.LinearFifo(CodegenJob, .Dynamic).init(gpa),
1454 .job_error = null,
1455 .done = false,
1456 },
14321457 .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
14331458 .win32_resource_work_queue = if (build_options.only_core_functionality) {} else std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa),
14341459 .astgen_work_queue = std.fifo.LinearFifo(Zcu.File.Index, .Dynamic).init(gpa),
......@@ -3310,7 +3335,21 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
33103335pub fn performAllTheWork(
33113336 comp: *Compilation,
33123337 main_progress_node: std.Progress.Node,
3313) error{ TimerUnsupported, OutOfMemory }!void {
3338) JobError!void {
3339 defer if (comp.module) |mod| {
3340 mod.sema_prog_node.end();
3341 mod.sema_prog_node = std.Progress.Node.none;
3342 mod.codegen_prog_node.end();
3343 mod.codegen_prog_node = std.Progress.Node.none;
3344 };
3345 try comp.performAllTheWorkInner(main_progress_node);
3346 if (!InternPool.single_threaded) if (comp.codegen_work.job_error) |job_error| return job_error;
3347}
3348
3349fn performAllTheWorkInner(
3350 comp: *Compilation,
3351 main_progress_node: std.Progress.Node,
3352) JobError!void {
33143353 // Here we queue up all the AstGen tasks first, followed by C object compilation.
33153354 // We wait until the AstGen tasks are all completed before proceeding to the
33163355 // (at least for now) single-threaded main work queue. However, C object compilation
......@@ -3410,16 +3449,20 @@ pub fn performAllTheWork(
34103449 mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
34113450 mod.codegen_prog_node = main_progress_node.start("Code Generation", 0);
34123451 }
3413 defer if (comp.module) |mod| {
3414 mod.sema_prog_node.end();
3415 mod.sema_prog_node = undefined;
3416 mod.codegen_prog_node.end();
3417 mod.codegen_prog_node = undefined;
3452
3453 if (!InternPool.single_threaded) comp.thread_pool.spawnWgId(&comp.work_queue_wait_group, codegenThread, .{comp});
3454 defer if (!InternPool.single_threaded) {
3455 {
3456 comp.codegen_work.mutex.lock();
3457 defer comp.codegen_work.mutex.unlock();
3458 comp.codegen_work.done = true;
3459 }
3460 comp.codegen_work.cond.signal();
34183461 };
34193462
34203463 while (true) {
34213464 if (comp.work_queue.readItem()) |work_item| {
3422 try processOneJob(0, comp, work_item, main_progress_node);
3465 try processOneJob(@intFromEnum(Zcu.PerThread.Id.main), comp, work_item, main_progress_node);
34233466 continue;
34243467 }
34253468 if (comp.module) |zcu| {
......@@ -3447,11 +3490,12 @@ pub fn performAllTheWork(
34473490 }
34483491}
34493492
3450fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progress.Node) !void {
3493const JobError = Allocator.Error;
3494
3495fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progress.Node) JobError!void {
34513496 switch (job) {
34523497 .codegen_decl => |decl_index| {
3453 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
3454 const decl = pt.zcu.declPtr(decl_index);
3498 const decl = comp.module.?.declPtr(decl_index);
34553499
34563500 switch (decl.analysis) {
34573501 .unreferenced => unreachable,
......@@ -3461,26 +3505,20 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
34613505 .sema_failure,
34623506 .codegen_failure,
34633507 .dependency_failure,
3464 => return,
3508 => {},
34653509
34663510 .complete => {
3467 const named_frame = tracy.namedFrame("codegen_decl");
3468 defer named_frame.end();
3469
34703511 assert(decl.has_tv);
3471
3472 try pt.linkerUpdateDecl(decl_index);
3473 return;
3512 try comp.queueCodegenJob(tid, .{ .decl = decl_index });
34743513 },
34753514 }
34763515 },
34773516 .codegen_func => |func| {
3478 const named_frame = tracy.namedFrame("codegen_func");
3479 defer named_frame.end();
3480
3481 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
34823517 // This call takes ownership of `func.air`.
3483 try pt.linkerUpdateFunc(func.func, func.air);
3518 try comp.queueCodegenJob(tid, .{ .func = .{
3519 .func = func.func,
3520 .air = func.air,
3521 } });
34843522 },
34853523 .analyze_func => |func| {
34863524 const named_frame = tracy.namedFrame("analyze_func");
......@@ -3772,6 +3810,61 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
37723810 }
37733811}
37743812
3813fn queueCodegenJob(comp: *Compilation, tid: usize, codegen_job: CodegenJob) !void {
3814 if (InternPool.single_threaded or
3815 !comp.module.?.backendSupportsFeature(.separate_thread))
3816 return processOneCodegenJob(tid, comp, codegen_job);
3817
3818 {
3819 comp.codegen_work.mutex.lock();
3820 defer comp.codegen_work.mutex.unlock();
3821 try comp.codegen_work.queue.writeItem(codegen_job);
3822 }
3823 comp.codegen_work.cond.signal();
3824}
3825
3826fn codegenThread(tid: usize, comp: *Compilation) void {
3827 comp.codegen_work.mutex.lock();
3828 defer comp.codegen_work.mutex.unlock();
3829
3830 while (true) {
3831 if (comp.codegen_work.queue.readItem()) |codegen_job| {
3832 comp.codegen_work.mutex.unlock();
3833 defer comp.codegen_work.mutex.lock();
3834
3835 processOneCodegenJob(tid, comp, codegen_job) catch |job_error| {
3836 comp.codegen_work.job_error = job_error;
3837 break;
3838 };
3839 continue;
3840 }
3841
3842 if (comp.codegen_work.done) break;
3843
3844 comp.codegen_work.cond.wait(&comp.codegen_work.mutex);
3845 }
3846}
3847
3848fn processOneCodegenJob(tid: usize, comp: *Compilation, codegen_job: CodegenJob) JobError!void {
3849 switch (codegen_job) {
3850 .decl => |decl_index| {
3851 const named_frame = tracy.namedFrame("codegen_decl");
3852 defer named_frame.end();
3853
3854 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
3855 try pt.linkerUpdateDecl(decl_index);
3856 },
3857 .func => |func| {
3858 const named_frame = tracy.namedFrame("codegen_func");
3859 defer named_frame.end();
3860
3861 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
3862 // This call takes ownership of `func.air`.
3863 try pt.linkerUpdateFunc(func.func, func.air);
3864 },
3865 }
3866}
3867
37753868fn workerDocsCopy(comp: *Compilation) void {
37763869 docsCopyFallible(comp) catch |err| {
37773870 return comp.lockAndSetMiscFailure(
src/Compilation/Config.zig+2-6
......@@ -440,12 +440,8 @@ pub fn resolve(options: Options) ResolveError!Config {
440440 };
441441 };
442442
443 const backend_supports_error_tracing = target_util.backendSupportsFeature(
444 target.cpu.arch,
445 target.ofmt,
446 use_llvm,
447 .error_return_trace,
448 );
443 const backend = target_util.zigBackend(target, use_llvm);
444 const backend_supports_error_tracing = target_util.backendSupportsFeature(backend, .error_return_trace);
449445
450446 const root_error_tracing = b: {
451447 if (options.root_error_tracing) |x| break :b x;
src/Zcu.zig+7-7
......@@ -64,8 +64,8 @@ root_mod: *Package.Module,
6464/// `root_mod` is the test runner, and `main_mod` is the user's source file which has the tests.
6565main_mod: *Package.Module,
6666std_mod: *Package.Module,
67sema_prog_node: std.Progress.Node = undefined,
68codegen_prog_node: std.Progress.Node = undefined,
67sema_prog_node: std.Progress.Node = std.Progress.Node.none,
68codegen_prog_node: std.Progress.Node = std.Progress.Node.none,
6969
7070/// Used by AstGen worker to load and store ZIR cache.
7171global_zir_cache: Compilation.Directory,
......@@ -3557,13 +3557,13 @@ pub const Feature = enum {
35573557 /// to generate better machine code in the backends. All backends should migrate to
35583558 /// enabling this feature.
35593559 safety_checked_instructions,
3560 /// If the backend supports running from another thread.
3561 separate_thread,
35603562};
35613563
3562pub fn backendSupportsFeature(zcu: Module, feature: Feature) bool {
3563 const cpu_arch = zcu.root_mod.resolved_target.result.cpu.arch;
3564 const ofmt = zcu.root_mod.resolved_target.result.ofmt;
3565 const use_llvm = zcu.comp.config.use_llvm;
3566 return target_util.backendSupportsFeature(cpu_arch, ofmt, use_llvm, feature);
3564pub fn backendSupportsFeature(zcu: Module, comptime feature: Feature) bool {
3565 const backend = target_util.zigBackend(zcu.root_mod.resolved_target.result, zcu.comp.config.use_llvm);
3566 return target_util.backendSupportsFeature(backend, feature);
35673567}
35683568
35693569pub const AtomicPtrAlignmentError = error{
src/Zcu/PerThread.zig+2-2
......@@ -2129,7 +2129,7 @@ pub fn populateTestFunctions(
21292129 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
21302130 defer {
21312131 zcu.sema_prog_node.end();
2132 zcu.sema_prog_node = undefined;
2132 zcu.sema_prog_node = std.Progress.Node.none;
21332133 }
21342134 try pt.ensureDeclAnalyzed(decl_index);
21352135 }
......@@ -2238,7 +2238,7 @@ pub fn populateTestFunctions(
22382238 zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
22392239 defer {
22402240 zcu.codegen_prog_node.end();
2241 zcu.codegen_prog_node = undefined;
2241 zcu.codegen_prog_node = std.Progress.Node.none;
22422242 }
22432243
22442244 try pt.linkerUpdateDecl(decl_index);
src/target.zig+36-14
......@@ -537,20 +537,42 @@ pub fn zigBackend(target: std.Target, use_llvm: bool) std.builtin.CompilerBacken
537537 };
538538}
539539
540pub fn backendSupportsFeature(
541 cpu_arch: std.Target.Cpu.Arch,
542 ofmt: std.Target.ObjectFormat,
543 use_llvm: bool,
544 feature: Feature,
545) bool {
540pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, comptime feature: Feature) bool {
546541 return switch (feature) {
547 .panic_fn => ofmt == .c or use_llvm or cpu_arch == .x86_64 or cpu_arch == .riscv64,
548 .panic_unwrap_error => ofmt == .c or use_llvm,
549 .safety_check_formatted => ofmt == .c or use_llvm,
550 .error_return_trace => use_llvm,
551 .is_named_enum_value => use_llvm,
552 .error_set_has_value => use_llvm or cpu_arch.isWasm(),
553 .field_reordering => ofmt == .c or use_llvm,
554 .safety_checked_instructions => use_llvm,
542 .panic_fn => switch (backend) {
543 .stage2_c, .stage2_llvm, .stage2_x86_64, .stage2_riscv64 => true,
544 else => false,
545 },
546 .panic_unwrap_error => switch (backend) {
547 .stage2_c, .stage2_llvm => true,
548 else => false,
549 },
550 .safety_check_formatted => switch (backend) {
551 .stage2_c, .stage2_llvm => true,
552 else => false,
553 },
554 .error_return_trace => switch (backend) {
555 .stage2_llvm => true,
556 else => false,
557 },
558 .is_named_enum_value => switch (backend) {
559 .stage2_llvm => true,
560 else => false,
561 },
562 .error_set_has_value => switch (backend) {
563 .stage2_llvm, .stage2_wasm => true,
564 else => false,
565 },
566 .field_reordering => switch (backend) {
567 .stage2_c, .stage2_llvm => true,
568 else => false,
569 },
570 .safety_checked_instructions => switch (backend) {
571 .stage2_llvm => true,
572 else => false,
573 },
574 .separate_thread => switch (backend) {
575 else => false,
576 },
555577 };
556578}