Skip to content

Commit 2ff4975

Browse files
committed
Compilation: introduce work stages for better work distribution
1 parent a1053e8 commit 2ff4975

11 files changed

+114
-57
lines changed

src/Compilation.zig

Lines changed: 93 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,15 @@ link_error_flags: link.File.ErrorFlags = .{},
101101
link_errors: std.ArrayListUnmanaged(link.File.ErrorMsg) = .{},
102102
lld_errors: std.ArrayListUnmanaged(LldError) = .{},
103103

104-
work_queue: std.fifo.LinearFifo(Job, .Dynamic),
104+
work_queues: [
105+
len: {
106+
var len: usize = 0;
107+
for (std.enums.values(Job.Tag)) |tag| {
108+
len = @max(Job.stage(tag) + 1, len);
109+
}
110+
break :len len;
111+
}
112+
]std.fifo.LinearFifo(Job, .Dynamic),
105113

106114
codegen_work: if (InternPool.single_threaded) void else struct {
107115
mutex: std.Thread.Mutex,
@@ -370,6 +378,20 @@ const Job = union(enum) {
370378

371379
/// The value is the index into `system_libs`.
372380
windows_import_lib: usize,
381+
382+
const Tag = @typeInfo(Job).Union.tag_type.?;
383+
fn stage(tag: Tag) usize {
384+
return switch (tag) {
385+
// Prioritize functions so that codegen can get to work on them on a
386+
// separate thread, while Sema goes back to its own work.
387+
.resolve_type_fully, .analyze_func, .codegen_func => 0,
388+
else => 1,
389+
};
390+
}
391+
comptime {
392+
// Job dependencies
393+
assert(stage(.resolve_type_fully) <= stage(.codegen_func));
394+
}
373395
};
374396

375397
const CodegenJob = union(enum) {
@@ -1452,7 +1474,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
14521474
.emit_asm = options.emit_asm,
14531475
.emit_llvm_ir = options.emit_llvm_ir,
14541476
.emit_llvm_bc = options.emit_llvm_bc,
1455-
.work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa),
1477+
.work_queues = .{std.fifo.LinearFifo(Job, .Dynamic).init(gpa)} ** @typeInfo(std.meta.FieldType(Compilation, .work_queues)).Array.len,
14561478
.codegen_work = if (InternPool.single_threaded) {} else .{
14571479
.mutex = .{},
14581480
.cond = .{},
@@ -1760,12 +1782,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
17601782
if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
17611783

17621784
if (glibc.needsCrtiCrtn(target)) {
1763-
try comp.work_queue.write(&[_]Job{
1785+
try comp.queueJobs(&[_]Job{
17641786
.{ .glibc_crt_file = .crti_o },
17651787
.{ .glibc_crt_file = .crtn_o },
17661788
});
17671789
}
1768-
try comp.work_queue.write(&[_]Job{
1790+
try comp.queueJobs(&[_]Job{
17691791
.{ .glibc_crt_file = .scrt1_o },
17701792
.{ .glibc_crt_file = .libc_nonshared_a },
17711793
.{ .glibc_shared_objects = {} },
@@ -1774,14 +1796,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
17741796
if (comp.wantBuildMuslFromSource()) {
17751797
if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
17761798

1777-
try comp.work_queue.ensureUnusedCapacity(6);
17781799
if (musl.needsCrtiCrtn(target)) {
1779-
comp.work_queue.writeAssumeCapacity(&[_]Job{
1800+
try comp.queueJobs(&[_]Job{
17801801
.{ .musl_crt_file = .crti_o },
17811802
.{ .musl_crt_file = .crtn_o },
17821803
});
17831804
}
1784-
comp.work_queue.writeAssumeCapacity(&[_]Job{
1805+
try comp.queueJobs(&[_]Job{
17851806
.{ .musl_crt_file = .crt1_o },
17861807
.{ .musl_crt_file = .scrt1_o },
17871808
.{ .musl_crt_file = .rcrt1_o },
@@ -1795,15 +1816,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
17951816
if (comp.wantBuildWasiLibcFromSource()) {
17961817
if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
17971818

1798-
// worst-case we need all components
1799-
try comp.work_queue.ensureUnusedCapacity(comp.wasi_emulated_libs.len + 2);
1800-
18011819
for (comp.wasi_emulated_libs) |crt_file| {
1802-
comp.work_queue.writeItemAssumeCapacity(.{
1820+
try comp.queueJob(.{
18031821
.wasi_libc_crt_file = crt_file,
18041822
});
18051823
}
1806-
comp.work_queue.writeAssumeCapacity(&[_]Job{
1824+
try comp.queueJobs(&[_]Job{
18071825
.{ .wasi_libc_crt_file = wasi_libc.execModelCrtFile(comp.config.wasi_exec_model) },
18081826
.{ .wasi_libc_crt_file = .libc_a },
18091827
});
@@ -1813,9 +1831,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18131831
if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
18141832

18151833
const crt_job: Job = .{ .mingw_crt_file = if (is_dyn_lib) .dllcrt2_o else .crt2_o };
1816-
try comp.work_queue.ensureUnusedCapacity(2);
1817-
comp.work_queue.writeItemAssumeCapacity(.{ .mingw_crt_file = .mingw32_lib });
1818-
comp.work_queue.writeItemAssumeCapacity(crt_job);
1834+
try comp.queueJobs(&.{
1835+
.{ .mingw_crt_file = .mingw32_lib },
1836+
crt_job,
1837+
});
18191838

18201839
// When linking mingw-w64 there are some import libs we always need.
18211840
for (mingw.always_link_libs) |name| {
@@ -1829,20 +1848,19 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18291848
// Generate Windows import libs.
18301849
if (target.os.tag == .windows) {
18311850
const count = comp.system_libs.count();
1832-
try comp.work_queue.ensureUnusedCapacity(count);
18331851
for (0..count) |i| {
1834-
comp.work_queue.writeItemAssumeCapacity(.{ .windows_import_lib = i });
1852+
try comp.queueJob(.{ .windows_import_lib = i });
18351853
}
18361854
}
18371855
if (comp.wantBuildLibUnwindFromSource()) {
1838-
try comp.work_queue.writeItem(.{ .libunwind = {} });
1856+
try comp.queueJob(.{ .libunwind = {} });
18391857
}
18401858
if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) {
1841-
try comp.work_queue.writeItem(.libcxx);
1842-
try comp.work_queue.writeItem(.libcxxabi);
1859+
try comp.queueJob(.libcxx);
1860+
try comp.queueJob(.libcxxabi);
18431861
}
18441862
if (build_options.have_llvm and comp.config.any_sanitize_thread) {
1845-
try comp.work_queue.writeItem(.libtsan);
1863+
try comp.queueJob(.libtsan);
18461864
}
18471865

18481866
if (target.isMinGW() and comp.config.any_non_single_threaded) {
@@ -1872,7 +1890,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18721890
if (!comp.skip_linker_dependencies and is_exe_or_dyn_lib and
18731891
!comp.config.link_libc and capable_of_building_zig_libc)
18741892
{
1875-
try comp.work_queue.writeItem(.{ .zig_libc = {} });
1893+
try comp.queueJob(.{ .zig_libc = {} });
18761894
}
18771895
}
18781896

@@ -1883,7 +1901,7 @@ pub fn destroy(comp: *Compilation) void {
18831901
if (comp.bin_file) |lf| lf.destroy();
18841902
if (comp.module) |zcu| zcu.deinit();
18851903
comp.cache_use.deinit();
1886-
comp.work_queue.deinit();
1904+
for (comp.work_queues) |work_queue| work_queue.deinit();
18871905
if (!InternPool.single_threaded) comp.codegen_work.queue.deinit();
18881906
comp.c_object_work_queue.deinit();
18891907
if (!build_options.only_core_functionality) {
@@ -2199,13 +2217,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
21992217
}
22002218
}
22012219

2202-
try comp.work_queue.writeItem(.{ .analyze_mod = std_mod });
2220+
try comp.queueJob(.{ .analyze_mod = std_mod });
22032221
if (comp.config.is_test) {
2204-
try comp.work_queue.writeItem(.{ .analyze_mod = zcu.main_mod });
2222+
try comp.queueJob(.{ .analyze_mod = zcu.main_mod });
22052223
}
22062224

22072225
if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| {
2208-
try comp.work_queue.writeItem(.{ .analyze_mod = compiler_rt_mod });
2226+
try comp.queueJob(.{ .analyze_mod = compiler_rt_mod });
22092227
}
22102228
}
22112229

@@ -3095,6 +3113,39 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
30953113
for (zcu.failed_embed_files.values()) |error_msg| {
30963114
try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
30973115
}
3116+
{
3117+
const SortOrder = struct {
3118+
zcu: *Zcu,
3119+
err: *?Error,
3120+
3121+
const Error = @typeInfo(
3122+
@typeInfo(@TypeOf(Zcu.SrcLoc.span)).Fn.return_type.?,
3123+
).ErrorUnion.error_set;
3124+
3125+
pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {
3126+
if (ctx.err.*) |_| return lhs_index < rhs_index;
3127+
const errors = ctx.zcu.failed_analysis.values();
3128+
const lhs_src_loc = errors[lhs_index].src_loc.upgrade(ctx.zcu);
3129+
const rhs_src_loc = errors[rhs_index].src_loc.upgrade(ctx.zcu);
3130+
return if (lhs_src_loc.file_scope != rhs_src_loc.file_scope) std.mem.order(
3131+
u8,
3132+
lhs_src_loc.file_scope.sub_file_path,
3133+
rhs_src_loc.file_scope.sub_file_path,
3134+
).compare(.lt) else (lhs_src_loc.span(ctx.zcu.gpa) catch |e| {
3135+
ctx.err.* = e;
3136+
return lhs_index < rhs_index;
3137+
}).main < (rhs_src_loc.span(ctx.zcu.gpa) catch |e| {
3138+
ctx.err.* = e;
3139+
return lhs_index < rhs_index;
3140+
}).main;
3141+
}
3142+
};
3143+
var err: ?SortOrder.Error = null;
3144+
// This leaves `zcu.failed_analysis` an invalid state, but we do not
3145+
// need lookups anymore anyway.
3146+
zcu.failed_analysis.entries.sort(SortOrder{ .zcu = zcu, .err = &err });
3147+
if (err) |e| return e;
3148+
}
30983149
for (zcu.failed_analysis.keys(), zcu.failed_analysis.values()) |anal_unit, error_msg| {
30993150
const decl_index = switch (anal_unit.unwrap()) {
31003151
.decl => |d| d,
@@ -3543,18 +3594,18 @@ fn performAllTheWorkInner(
35433594
comp.codegen_work.cond.signal();
35443595
};
35453596

3546-
while (true) {
3547-
if (comp.work_queue.readItem()) |work_item| {
3548-
try processOneJob(@intFromEnum(Zcu.PerThread.Id.main), comp, work_item, main_progress_node);
3549-
continue;
3550-
}
3597+
work: while (true) {
3598+
for (&comp.work_queues) |*work_queue| if (work_queue.readItem()) |job| {
3599+
try processOneJob(@intFromEnum(Zcu.PerThread.Id.main), comp, job, main_progress_node);
3600+
continue :work;
3601+
};
35513602
if (comp.module) |zcu| {
35523603
// If there's no work queued, check if there's anything outdated
35533604
// which we need to work on, and queue it if so.
35543605
if (try zcu.findOutdatedToAnalyze()) |outdated| {
35553606
switch (outdated.unwrap()) {
3556-
.decl => |decl| try comp.work_queue.writeItem(.{ .analyze_decl = decl }),
3557-
.func => |func| try comp.work_queue.writeItem(.{ .analyze_func = func }),
3607+
.decl => |decl| try comp.queueJob(.{ .analyze_decl = decl }),
3608+
.func => |func| try comp.queueJob(.{ .analyze_func = func }),
35583609
}
35593610
continue;
35603611
}
@@ -3575,6 +3626,14 @@ fn performAllTheWorkInner(
35753626

35763627
const JobError = Allocator.Error;
35773628

3629+
pub fn queueJob(comp: *Compilation, job: Job) !void {
3630+
try comp.work_queues[Job.stage(job)].writeItem(job);
3631+
}
3632+
3633+
pub fn queueJobs(comp: *Compilation, jobs: []const Job) !void {
3634+
for (jobs) |job| try comp.queueJob(job);
3635+
}
3636+
35783637
fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progress.Node) JobError!void {
35793638
switch (job) {
35803639
.codegen_decl => |decl_index| {
@@ -6478,7 +6537,7 @@ pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void {
64786537
};
64796538
const target = comp.root_mod.resolved_target.result;
64806539
if (target.os.tag == .windows and target.ofmt != .c) {
6481-
try comp.work_queue.writeItem(.{
6540+
try comp.queueJob(.{
64826541
.windows_import_lib = comp.system_libs.count() - 1,
64836542
});
64846543
}

src/Sema.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2853,7 +2853,7 @@ fn zirStructDecl(
28532853
}
28542854

28552855
try pt.finalizeAnonDecl(new_decl_index);
2856-
try mod.comp.work_queue.writeItem(.{ .resolve_type_fully = wip_ty.index });
2856+
try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
28572857
try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .decl = new_decl_index }));
28582858
return Air.internedToRef(wip_ty.finish(ip, new_decl_index, new_namespace_index));
28592859
}
@@ -3358,7 +3358,7 @@ fn zirUnionDecl(
33583358
}
33593359

33603360
try pt.finalizeAnonDecl(new_decl_index);
3361-
try mod.comp.work_queue.writeItem(.{ .resolve_type_fully = wip_ty.index });
3361+
try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
33623362
try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .decl = new_decl_index }));
33633363
return Air.internedToRef(wip_ty.finish(ip, new_decl_index, new_namespace_index));
33643364
}
@@ -22203,7 +22203,7 @@ fn reifyUnion(
2220322203
loaded_union.setStatus(ip, .have_field_types);
2220422204

2220522205
try pt.finalizeAnonDecl(new_decl_index);
22206-
try mod.comp.work_queue.writeItem(.{ .resolve_type_fully = wip_ty.index });
22206+
try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
2220722207
try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .decl = new_decl_index }));
2220822208
return Air.internedToRef(wip_ty.finish(ip, new_decl_index, .none));
2220922209
}
@@ -22470,7 +22470,7 @@ fn reifyStruct(
2247022470
}
2247122471

2247222472
try pt.finalizeAnonDecl(new_decl_index);
22473-
try mod.comp.work_queue.writeItem(.{ .resolve_type_fully = wip_ty.index });
22473+
try mod.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
2247422474
try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .decl = new_decl_index }));
2247522475
return Air.internedToRef(wip_ty.finish(ip, new_decl_index, .none));
2247622476
}

src/Zcu.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2983,11 +2983,11 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index)
29832983

29842984
// Decl itself is safely analyzed, and body analysis is not yet queued
29852985

2986-
try mod.comp.work_queue.writeItem(.{ .analyze_func = func_index });
2986+
try mod.comp.queueJob(.{ .analyze_func = func_index });
29872987
if (mod.emit_h != null) {
29882988
// TODO: we ideally only want to do this if the function's type changed
29892989
// since the last update
2990-
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });
2990+
try mod.comp.queueJob(.{ .emit_h_decl = decl_index });
29912991
}
29922992
func.setAnalysisState(ip, .queued);
29932993
}

src/Zcu/PerThread.zig

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
729729
return;
730730
}
731731

732-
try comp.work_queue.writeItem(.{ .codegen_func = .{
732+
try comp.queueJob(.{ .codegen_func = .{
733733
.func = func_index,
734734
.air = air,
735735
} });
@@ -903,7 +903,7 @@ fn getFileRootStruct(
903903
decl.analysis = .complete;
904904

905905
try pt.scanNamespace(namespace_index, decls, decl);
906-
try zcu.comp.work_queue.writeItem(.{ .resolve_type_fully = wip_ty.index });
906+
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
907907
return wip_ty.finish(ip, decl_index, namespace_index.toOptional());
908908
}
909909

@@ -1311,10 +1311,10 @@ fn semaDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
13111311
// codegen backend wants full access to the Decl Type.
13121312
try decl_ty.resolveFully(pt);
13131313

1314-
try zcu.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
1314+
try zcu.comp.queueJob(.{ .codegen_decl = decl_index });
13151315

13161316
if (result.invalidate_decl_ref and zcu.emit_h != null) {
1317-
try zcu.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });
1317+
try zcu.comp.queueJob(.{ .emit_h_decl = decl_index });
13181318
}
13191319
}
13201320

@@ -1740,8 +1740,6 @@ pub fn scanNamespace(
17401740
var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
17411741
defer seen_decls.deinit(gpa);
17421742

1743-
try zcu.comp.work_queue.ensureUnusedCapacity(decls.len);
1744-
17451743
namespace.decls.clearRetainingCapacity();
17461744
try namespace.decls.ensureTotalCapacity(gpa, decls.len);
17471745

@@ -1967,7 +1965,7 @@ const ScanDeclIter = struct {
19671965
log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{}' decl_index={d}", .{
19681966
namespace.fileScope(zcu).sub_file_path, decl_name.fmt(ip), decl_index,
19691967
});
1970-
comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = decl_index });
1968+
try comp.queueJob(.{ .analyze_decl = decl_index });
19711969
}
19721970
}
19731971

@@ -1976,7 +1974,7 @@ const ScanDeclIter = struct {
19761974
// updated line numbers. Look into this!
19771975
// TODO Look into detecting when this would be unnecessary by storing enough state
19781976
// in `Decl` to notice that the line number did not change.
1979-
comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index });
1977+
try comp.queueJob(.{ .update_line_number = decl_index });
19801978
}
19811979
}
19821980
};
@@ -1991,7 +1989,7 @@ pub fn abortAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) void {
19911989
/// Finalize the creation of an anon decl.
19921990
pub fn finalizeAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) Allocator.Error!void {
19931991
if (pt.zcu.declPtr(decl_index).typeOf(pt.zcu).isFnOrHasRuntimeBits(pt)) {
1994-
try pt.zcu.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
1992+
try pt.zcu.comp.queueJob(.{ .codegen_decl = decl_index });
19951993
}
19961994
}
19971995

src/codegen/llvm.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1625,7 +1625,7 @@ pub const Object = struct {
16251625
llvm_arg_i += 1;
16261626

16271627
const alignment = param_ty.abiAlignment(pt).toLlvm();
1628-
const arg_ptr = try buildAllocaInner(&wip, param_llvm_ty, alignment, target);
1628+
const arg_ptr = try buildAllocaInner(&wip, param.typeOfWip(&wip), alignment, target);
16291629
_ = try wip.store(.normal, param, arg_ptr, alignment);
16301630

16311631
args.appendAssumeCapacity(if (isByRef(param_ty, pt))

test/cases/compile_errors/bogus_method_call_on_slice.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ pub export fn entry2() void {
1616
// backend=stage2
1717
// target=native
1818
//
19+
// :3:6: error: no field or member function named 'copy' in '[]const u8'
1920
// :9:8: error: no field or member function named 'bar' in '@TypeOf(.{})'
2021
// :12:18: error: no field or member function named 'bar' in 'struct{comptime foo: comptime_int = 1}'
21-
// :3:6: error: no field or member function named 'copy' in '[]const u8'

0 commit comments

Comments
 (0)