diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-06-16 20:23:22 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2022-06-17 16:38:59 -0700 |
| commit | 5cd548e53081428d0e6b4a6b5a305317052c133a (patch) | |
| tree | 4beb45ef87a73007a11e004a2fc52e35d8a6dc8e /src/Compilation.zig | |
| parent | b4f3e69342d176ad7a2572cf4fee704094faaada (diff) | |
| download | zig-5cd548e53081428d0e6b4a6b5a305317052c133a.tar.gz zig-5cd548e53081428d0e6b4a6b5a305317052c133a.zip | |
Compilation: multi-thread compiler-rt
compiler_rt_lib and compiler_rt_obj are extracted from the generic
JobQueue into simple boolean flags, and then handled explicitly inside
performAllTheWork().
Introduced generic handling of allocation failure and made
setMiscFailure not return a possible error.
Building the compiler-rt static library now takes advantage of
Compilation's ThreadPool. This introduced a problem, however, because
now each of the object files of compiler-rt all perform AstGen for the
full standard library and compiler-rt files. Even though all of them end
up being cache hits except for the first ones, this is wasteful - O(N*M)
where N is number of compilation units inside compiler-rt and M is the
number of .zig files in the standard library and compiler-rt combined.
More importantly, however, it causes a deadlock, because each thread
interacts with a file system lock for doing AstGen on files, and threads
end up waiting for each other. This will need to be handled with a
process-level file caching system, or some other creative solution.
Diffstat (limited to 'src/Compilation.zig')
| -rw-r--r-- | src/Compilation.zig | 193 |
1 files changed, 115 insertions, 78 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 2858a28f42..65a2ad92b4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -93,6 +93,9 @@ unwind_tables: bool, test_evented_io: bool, debug_compiler_runtime_libs: bool, debug_compile_errors: bool, +job_queued_compiler_rt_lib: bool = false, +job_queued_compiler_rt_obj: bool = false, +alloc_failure_occurred: bool = false, c_source_files: []const CSourceFile, clang_argv: []const []const u8, @@ -130,11 +133,11 @@ libssp_static_lib: ?CRTFile = null, /// Populated when we build the libc static library. A Job to build this is placed in the queue /// and resolved before calling linker.flush(). libc_static_lib: ?CRTFile = null, -/// Populated when we build the libcompiler_rt static library. A Job to build this is placed in the queue -/// and resolved before calling linker.flush(). +/// Populated when we build the libcompiler_rt static library. A Job to build this is indicated +/// by setting `job_queued_compiler_rt_lib` and resolved before calling linker.flush(). compiler_rt_lib: ?CRTFile = null, -/// Populated when we build the compiler_rt_obj object. A Job to build this is placed in the queue -/// and resolved before calling linker.flush(). +/// Populated when we build the compiler_rt_obj object. A Job to build this is indicated +/// by setting `job_queued_compiler_rt_obj` and resolved before calling linker.flush(). compiler_rt_obj: ?CRTFile = null, glibc_so_files: ?glibc.BuiltSharedObjects = null, @@ -224,8 +227,6 @@ const Job = union(enum) { libcxxabi: void, libtsan: void, libssp: void, - compiler_rt_lib: void, - compiler_rt_obj: void, /// needed when not linking libc and using LLVM for code generation because it generates /// calls to, for example, memcpy and memset. zig_libc: void, @@ -1925,13 +1926,13 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) { if (is_exe_or_dyn_lib) { log.debug("queuing a job to build compiler_rt_lib", .{}); - try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} }); + comp.job_queued_compiler_rt_lib = true; } else if (options.output_mode != .Obj) { log.debug("queuing a job to build compiler_rt_obj", .{}); // If build-obj with -fcompiler-rt is requested, that is handled specially // elsewhere. In this case we are making a static library, so we ask // for a compiler-rt object to put in it. - try comp.work_queue.writeItem(.{ .compiler_rt_obj = {} }); + comp.job_queued_compiler_rt_obj = true; } } if (needs_c_symbols) { @@ -2021,6 +2022,7 @@ pub fn destroy(self: *Compilation) void { } pub fn clearMiscFailures(comp: *Compilation) void { + comp.alloc_failure_occurred = false; for (comp.misc_failures.values()) |*value| { value.deinit(comp.gpa); } @@ -2533,8 +2535,10 @@ pub fn makeBinFileWritable(self: *Compilation) !void { return self.bin_file.makeWritable(); } +/// This function is temporally single-threaded. pub fn totalErrorCount(self: *Compilation) usize { - var total: usize = self.failed_c_objects.count() + self.misc_failures.count(); + var total: usize = self.failed_c_objects.count() + self.misc_failures.count() + + @boolToInt(self.alloc_failure_occurred); if (self.bin_file.options.module) |module| { total += module.failed_exports.count(); @@ -2591,6 +2595,7 @@ pub fn totalErrorCount(self: *Compilation) usize { return total; } +/// This function is temporally single-threaded. pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { var arena = std.heap.ArenaAllocator.init(self.gpa); errdefer arena.deinit(); @@ -2623,6 +2628,9 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { for (self.misc_failures.values()) |*value| { try AllErrors.addPlainWithChildren(&arena, &errors, value.msg, value.children); } + if (self.alloc_failure_occurred) { + try AllErrors.addPlain(&arena, &errors, "memory allocation failure"); + } if (self.bin_file.options.module) |module| { { var it = module.failed_files.iterator(); @@ -2737,9 +2745,15 @@ pub fn performAllTheWork( var embed_file_prog_node = main_progress_node.start("Detect @embedFile updates", comp.embed_file_work_queue.count); defer embed_file_prog_node.end(); + // +1 for the link step + var compiler_rt_prog_node = main_progress_node.start("compiler_rt", compiler_rt.sources.len + 1); + defer compiler_rt_prog_node.end(); + comp.work_queue_wait_group.reset(); defer comp.work_queue_wait_group.wait(); + const use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1; + { const astgen_frame = tracy.namedFrame("astgen"); defer astgen_frame.end(); @@ -2782,9 +2796,28 @@ pub fn performAllTheWork( comp, c_object, &c_obj_prog_node, &comp.work_queue_wait_group, }); } + + if (comp.job_queued_compiler_rt_lib) { + comp.job_queued_compiler_rt_lib = false; + + if (use_stage1) { + // stage1 LLVM backend uses the global context and thus cannot be used in + // a multi-threaded context. + buildCompilerRtOneShot(comp, .Lib, &comp.compiler_rt_lib); + } else { + comp.work_queue_wait_group.start(); + try comp.thread_pool.spawn(workerBuildCompilerRtLib, .{ + comp, &compiler_rt_prog_node, &comp.work_queue_wait_group, + }); + } + } + + if (comp.job_queued_compiler_rt_obj) { + comp.job_queued_compiler_rt_obj = false; + buildCompilerRtOneShot(comp, .Obj, &comp.compiler_rt_obj); + } } - const use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1; if (!use_stage1) { const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls"); defer outdated_and_deleted_decls_frame.end(); @@ -2997,7 +3030,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { module.semaPkg(pkg) catch |err| switch (err) { error.CurrentWorkingDirectoryUnlinked, error.Unexpected, - => try comp.setMiscFailure( + => comp.lockAndSetMiscFailure( .analyze_pkg, "unexpected problem analyzing package '{s}'", .{pkg.root_src_path}, @@ -3012,7 +3045,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { glibc.buildCRTFile(comp, crt_file) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{ + comp.lockAndSetMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{ @errorName(err), }); }; @@ -3023,7 +3056,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { glibc.buildSharedObjects(comp) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .glibc_shared_objects, "unable to build glibc shared objects: {s}", .{@errorName(err)}, @@ -3036,7 +3069,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { musl.buildCRTFile(comp, crt_file) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .musl_crt_file, "unable to build musl CRT file: {s}", .{@errorName(err)}, @@ -3049,7 +3082,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { mingw.buildCRTFile(comp, crt_file) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .mingw_crt_file, "unable to build mingw-w64 CRT file: {s}", .{@errorName(err)}, @@ -3063,7 +3096,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { const link_lib = comp.bin_file.options.system_libs.keys()[index]; mingw.buildImportLib(comp, link_lib) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .windows_import_lib, "unable to generate DLL import .lib file: {s}", .{@errorName(err)}, @@ -3076,7 +3109,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { libunwind.buildStaticLib(comp) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .libunwind, "unable to build libunwind: {s}", .{@errorName(err)}, @@ -3089,7 +3122,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { libcxx.buildLibCXX(comp) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .libcxx, "unable to build libcxx: {s}", .{@errorName(err)}, @@ -3102,7 +3135,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { libcxx.buildLibCXXABI(comp) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .libcxxabi, "unable to build libcxxabi: {s}", .{@errorName(err)}, @@ -3115,7 +3148,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { libtsan.buildTsan(comp) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .libtsan, "unable to build TSAN library: {s}", .{@errorName(err)}, @@ -3128,49 +3161,13 @@ fn processOneJob(comp: *Compilation, job: Job) !void { wasi_libc.buildCRTFile(comp, crt_file) catch |err| { // TODO Surface more error details. - try comp.setMiscFailure( + comp.lockAndSetMiscFailure( .wasi_libc_crt_file, "unable to build WASI libc CRT file: {s}", .{@errorName(err)}, ); }; }, - .compiler_rt_lib => { - const named_frame = tracy.namedFrame("compiler_rt_lib"); - defer named_frame.end(); - - compiler_rt.buildCompilerRtLib( - comp, - &comp.compiler_rt_lib, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.SubCompilationFailed => return, // error reported already - else => try comp.setMiscFailure( - .compiler_rt, - "unable to build compiler_rt: {s}", - .{@errorName(err)}, - ), - }; - }, - .compiler_rt_obj => { - const named_frame = tracy.namedFrame("compiler_rt_obj"); - defer named_frame.end(); - - comp.buildOutputFromZig( - "compiler_rt.zig", - .Obj, - &comp.compiler_rt_obj, - .compiler_rt, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.SubCompilationFailed => return, // error reported already - else => try comp.setMiscFailure( - .compiler_rt, - "unable to build compiler_rt: {s}", - .{@errorName(err)}, - ), - }; - }, .libssp => { const named_frame = tracy.namedFrame("libssp"); defer named_frame.end(); @@ -3183,7 +3180,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.SubCompilationFailed => return, // error reported already - else => try comp.setMiscFailure( + else => comp.lockAndSetMiscFailure( .libssp, "unable to build libssp: {s}", .{@errorName(err)}, @@ -3202,7 +3199,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void { ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.SubCompilationFailed => return, // error reported already - else => try comp.setMiscFailure( + else => comp.lockAndSetMiscFailure( .zig_libc, "unable to build zig's multitarget libc: {s}", .{@errorName(err)}, @@ -3306,11 +3303,7 @@ fn workerUpdateBuiltinZigFile( comp.setMiscFailure(.write_builtin_zig, "unable to write builtin.zig to {s}: {s}", .{ dir_path, @errorName(err), - }) catch |oom| switch (oom) { - error.OutOfMemory => log.err("unable to write builtin.zig to {s}: {s}", .{ - dir_path, @errorName(err), - }), - }; + }); }; } @@ -3524,6 +3517,38 @@ fn workerUpdateCObject( }; } +fn buildCompilerRtOneShot( + comp: *Compilation, + output_mode: std.builtin.OutputMode, + out: *?CRTFile, +) void { + comp.buildOutputFromZig("compiler_rt.zig", output_mode, out, .compiler_rt) catch |err| switch (err) { + error.SubCompilationFailed => return, // error reported already + else => comp.lockAndSetMiscFailure( + .compiler_rt, + "unable to build compiler_rt: {s}", + .{@errorName(err)}, + ), + }; +} + +fn workerBuildCompilerRtLib( + comp: *Compilation, + progress_node: *std.Progress.Node, + wg: *WaitGroup, +) void { + defer wg.finish(); + + compiler_rt.buildCompilerRtLib(comp, progress_node) catch |err| switch (err) { + error.SubCompilationFailed => return, // error reported already + else => comp.lockAndSetMiscFailure( + .compiler_rt, + "unable to build compiler_rt: {s}", + .{@errorName(err)}, + ), + }; +} + fn reportRetryableCObjectError( comp: *Compilation, c_object: *CObject, @@ -4622,14 +4647,21 @@ fn wantBuildLibUnwindFromSource(comp: *Compilation) bool { comp.bin_file.options.object_format != .c; } -fn setMiscFailure( +fn setAllocFailure(comp: *Compilation) void { + log.debug("memory allocation failure", .{}); + comp.alloc_failure_occurred = true; +} + +/// Assumes that Compilation mutex is locked. +/// See also `lockAndSetMiscFailure`. +pub fn setMiscFailure( comp: *Compilation, tag: MiscTask, comptime format: []const u8, args: anytype, -) Allocator.Error!void { - try comp.misc_failures.ensureUnusedCapacity(comp.gpa, 1); - const msg = try std.fmt.allocPrint(comp.gpa, format, args); +) void { + comp.misc_failures.ensureUnusedCapacity(comp.gpa, 1) catch return comp.setAllocFailure(); + const msg = std.fmt.allocPrint(comp.gpa, format, args) catch return comp.setAllocFailure(); const gop = comp.misc_failures.getOrPutAssumeCapacity(tag); if (gop.found_existing) { gop.value_ptr.deinit(comp.gpa); @@ -4637,6 +4669,19 @@ fn setMiscFailure( gop.value_ptr.* = .{ .msg = msg }; } +/// See also `setMiscFailure`. +pub fn lockAndSetMiscFailure( + comp: *Compilation, + tag: MiscTask, + comptime format: []const u8, + args: anytype, +) void { + comp.mutex.lock(); + defer comp.mutex.unlock(); + + return setMiscFailure(comp, tag, format, args); +} + pub fn dump_argv(argv: []const []const u8) void { for (argv[0 .. argv.len - 1]) |arg| { std.debug.print("{s} ", .{arg}); @@ -4896,7 +4941,7 @@ pub fn updateSubCompilation(sub_compilation: *Compilation) !void { } } -pub fn buildOutputFromZig( +fn buildOutputFromZig( comp: *Compilation, src_basename: []const u8, output_mode: std.builtin.OutputMode, @@ -4913,15 +4958,7 @@ pub fn buildOutputFromZig( .root_src_path = src_basename, }; defer main_pkg.deinitTable(comp.gpa); - - const root_name = root_name: { - const basename = if (std.fs.path.dirname(src_basename)) |dirname| - src_basename[dirname.len + 1 ..] - else - src_basename; - const root_name = basename[0 .. basename.len - std.fs.path.extension(basename).len]; - break :root_name root_name; - }; + const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len]; const target = comp.getTarget(); const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{ .root_name = root_name, |
