authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-12-20 19:36:50-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-01-15 15:11:36-08:00
logd1cde847a367c526c63d65714583189fa2912731
tree96f1c2f65c9fe7f84381356e733f14b1341e328b
parent694b129d8960a9c3548dccb2a8f0c82f44fbafa9

implement the prelink phase in the frontend

this strategy uses a "postponed" queue to handle codegen tasks that spawn too early. there's probably a better way.

4 files changed, 110 insertions(+), 23 deletions(-)

src/Compilation.zig+34-3
......@@ -113,6 +113,12 @@ link_diags: link.Diags,
113113link_task_queue: ThreadSafeQueue(link.Task) = .empty,
114114/// Ensure only 1 simultaneous call to `flushTaskQueue`.
115115link_task_queue_safety: std.debug.SafetyLock = .{},
116/// If any tasks are queued up that depend on prelink being finished, they are moved
117/// here until prelink finishes.
118link_task_queue_postponed: std.ArrayListUnmanaged(link.Task) = .empty,
119/// Initialized with how many link input tasks are expected. After this reaches zero
120/// the linker will begin the prelink phase.
121remaining_prelink_tasks: u32,
116122
117123work_queues: [
118124 len: {
......@@ -1515,6 +1521,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
15151521 .file_system_inputs = options.file_system_inputs,
15161522 .parent_whole_cache = options.parent_whole_cache,
15171523 .link_diags = .init(gpa),
1524 .remaining_prelink_tasks = 0,
15181525 };
15191526
15201527 // Prevent some footguns by making the "any" fields of config reflect
......@@ -1780,10 +1787,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
17801787 inline for (fields) |field| {
17811788 if (@field(paths, field.name)) |path| {
17821789 comp.link_task_queue.shared.appendAssumeCapacity(.{ .load_object = path });
1790 comp.remaining_prelink_tasks += 1;
17831791 }
17841792 }
17851793 // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`.
17861794 comp.link_task_queue.shared.appendAssumeCapacity(.load_host_libc);
1795 comp.remaining_prelink_tasks += 1;
17871796 } else if (target.isMusl() and !target.isWasm()) {
17881797 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
17891798
......@@ -1792,14 +1801,17 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
17921801 .{ .musl_crt_file = .crti_o },
17931802 .{ .musl_crt_file = .crtn_o },
17941803 });
1804 comp.remaining_prelink_tasks += 2;
17951805 }
17961806 if (musl.needsCrt0(comp.config.output_mode, comp.config.link_mode, comp.config.pie)) |f| {
17971807 try comp.queueJobs(&.{.{ .musl_crt_file = f }});
1808 comp.remaining_prelink_tasks += 1;
17981809 }
17991810 try comp.queueJobs(&.{.{ .musl_crt_file = switch (comp.config.link_mode) {
18001811 .static => .libc_a,
18011812 .dynamic => .libc_so,
18021813 } }});
1814 comp.remaining_prelink_tasks += 1;
18031815 } else if (target.isGnuLibC()) {
18041816 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
18051817
......@@ -1808,14 +1820,18 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18081820 .{ .glibc_crt_file = .crti_o },
18091821 .{ .glibc_crt_file = .crtn_o },
18101822 });
1823 comp.remaining_prelink_tasks += 2;
18111824 }
18121825 if (glibc.needsCrt0(comp.config.output_mode)) |f| {
18131826 try comp.queueJobs(&.{.{ .glibc_crt_file = f }});
1827 comp.remaining_prelink_tasks += 1;
18141828 }
18151829 try comp.queueJobs(&[_]Job{
18161830 .{ .glibc_shared_objects = {} },
18171831 .{ .glibc_crt_file = .libc_nonshared_a },
18181832 });
1833 comp.remaining_prelink_tasks += 1;
1834 comp.remaining_prelink_tasks += glibc.sharedObjectsCount(&target);
18191835 } else if (target.isWasm() and target.os.tag == .wasi) {
18201836 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
18211837
......@@ -1823,11 +1839,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18231839 try comp.queueJob(.{
18241840 .wasi_libc_crt_file = crt_file,
18251841 });
1842 comp.remaining_prelink_tasks += 1;
18261843 }
18271844 try comp.queueJobs(&[_]Job{
18281845 .{ .wasi_libc_crt_file = wasi_libc.execModelCrtFile(comp.config.wasi_exec_model) },
18291846 .{ .wasi_libc_crt_file = .libc_a },
18301847 });
1848 comp.remaining_prelink_tasks += 2;
18311849 } else if (target.isMinGW()) {
18321850 if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
18331851
......@@ -1836,6 +1854,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18361854 .{ .mingw_crt_file = .mingw32_lib },
18371855 crt_job,
18381856 });
1857 comp.remaining_prelink_tasks += 2;
18391858
18401859 // When linking mingw-w64 there are some import libs we always need.
18411860 try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len);
......@@ -1847,6 +1866,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18471866 }
18481867 } else if (target.os.tag == .freestanding and capable_of_building_zig_libc) {
18491868 try comp.queueJob(.{ .zig_libc = {} });
1869 comp.remaining_prelink_tasks += 1;
18501870 } else {
18511871 return error.LibCUnavailable;
18521872 }
......@@ -1858,16 +1878,20 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18581878 for (0..count) |i| {
18591879 try comp.queueJob(.{ .windows_import_lib = i });
18601880 }
1881 comp.remaining_prelink_tasks += @intCast(count);
18611882 }
18621883 if (comp.wantBuildLibUnwindFromSource()) {
18631884 try comp.queueJob(.{ .libunwind = {} });
1885 comp.remaining_prelink_tasks += 1;
18641886 }
18651887 if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) {
18661888 try comp.queueJob(.libcxx);
18671889 try comp.queueJob(.libcxxabi);
1890 comp.remaining_prelink_tasks += 2;
18681891 }
18691892 if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.any_sanitize_thread) {
18701893 try comp.queueJob(.libtsan);
1894 comp.remaining_prelink_tasks += 1;
18711895 }
18721896
18731897 if (target.isMinGW() and comp.config.any_non_single_threaded) {
......@@ -1886,21 +1910,25 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
18861910 if (is_exe_or_dyn_lib) {
18871911 log.debug("queuing a job to build compiler_rt_lib", .{});
18881912 comp.job_queued_compiler_rt_lib = true;
1913 comp.remaining_prelink_tasks += 1;
18891914 } else if (output_mode != .Obj) {
18901915 log.debug("queuing a job to build compiler_rt_obj", .{});
18911916 // In this case we are making a static library, so we ask
18921917 // for a compiler-rt object to put in it.
18931918 comp.job_queued_compiler_rt_obj = true;
1919 comp.remaining_prelink_tasks += 1;
18941920 }
18951921 }
18961922
18971923 if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) {
18981924 log.debug("queuing a job to build libfuzzer", .{});
18991925 comp.job_queued_fuzzer_lib = true;
1926 comp.remaining_prelink_tasks += 1;
19001927 }
19011928 }
19021929
19031930 try comp.link_task_queue.shared.append(gpa, .load_explicitly_provided);
1931 comp.remaining_prelink_tasks += 1;
19041932 }
19051933
19061934 return comp;
......@@ -1977,6 +2005,7 @@ pub fn destroy(comp: *Compilation) void {
19772005
19782006 comp.link_diags.deinit();
19792007 comp.link_task_queue.deinit(gpa);
2008 comp.link_task_queue_postponed.deinit(gpa);
19802009
19812010 comp.clearMiscFailures();
19822011
......@@ -3528,9 +3557,9 @@ pub fn performAllTheWork(
35283557
35293558 defer if (comp.zcu) |zcu| {
35303559 zcu.sema_prog_node.end();
3531 zcu.sema_prog_node = std.Progress.Node.none;
3560 zcu.sema_prog_node = .none;
35323561 zcu.codegen_prog_node.end();
3533 zcu.codegen_prog_node = std.Progress.Node.none;
3562 zcu.codegen_prog_node = .none;
35343563
35353564 zcu.generation += 1;
35363565 };
......@@ -3663,7 +3692,7 @@ fn performAllTheWorkInner(
36633692 try zcu.flushRetryableFailures();
36643693
36653694 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
3666 zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
3695 zcu.codegen_prog_node = if (comp.bin_file != null) main_progress_node.start("Code Generation", 0) else .none;
36673696 }
36683697
36693698 if (!comp.separateCodegenThreadOk()) {
......@@ -3693,6 +3722,8 @@ fn performAllTheWorkInner(
36933722 });
36943723 continue;
36953724 }
3725 zcu.sema_prog_node.end();
3726 zcu.sema_prog_node = .none;
36963727 }
36973728 break;
36983729 }
src/glibc.zig+12
......@@ -1217,6 +1217,18 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) !voi
12171217 });
12181218}
12191219
1220pub fn sharedObjectsCount(target: *const std.Target) u8 {
1221 const target_version = target.os.versionRange().gnuLibCVersion() orelse return 0;
1222 var count: u8 = 0;
1223 for (libs) |lib| {
1224 if (lib.removed_in) |rem_in| {
1225 if (target_version.order(rem_in) != .lt) continue;
1226 }
1227 count += 1;
1228 }
1229 return count;
1230}
1231
12201232fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {
12211233 const target_version = comp.getTarget().os.versionRange().gnuLibCVersion().?;
12221234
src/link.zig+60-18
......@@ -364,6 +364,7 @@ pub const File = struct {
364364 build_id: std.zig.BuildId,
365365 allow_shlib_undefined: bool,
366366 stack_size: u64,
367 post_prelink: bool = false,
367368
368369 /// Prevents other processes from clobbering files in the output directory
369370 /// of this linking operation.
......@@ -780,6 +781,8 @@ pub const File = struct {
780781 return;
781782 }
782783
784 assert(base.post_prelink);
785
783786 const use_lld = build_options.have_llvm and comp.config.use_lld;
784787 const output_mode = comp.config.output_mode;
785788 const link_mode = comp.config.link_mode;
......@@ -1007,7 +1010,8 @@ pub const File = struct {
10071010
10081011 /// Called when all linker inputs have been sent via `loadInput`. After
10091012 /// this, `loadInput` will not be called anymore.
1010 pub fn prelink(base: *File) FlushError!void {
1013 pub fn prelink(base: *File, prog_node: std.Progress.Node) FlushError!void {
1014 assert(!base.post_prelink);
10111015 const use_lld = build_options.have_llvm and base.comp.config.use_lld;
10121016 if (use_lld) return;
10131017
......@@ -1019,7 +1023,7 @@ pub const File = struct {
10191023 switch (base.tag) {
10201024 inline .wasm => |tag| {
10211025 dev.check(tag.devFeature());
1022 return @as(*tag.Type(), @fieldParentPtr("base", base)).prelink();
1026 return @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(prog_node);
10231027 },
10241028 else => {},
10251029 }
......@@ -1326,12 +1330,32 @@ pub const File = struct {
13261330/// from the rest of compilation. All tasks performed here are
13271331/// single-threaded with respect to one another.
13281332pub fn flushTaskQueue(tid: usize, comp: *Compilation) void {
1333 const diags = &comp.link_diags;
13291334 // As soon as check() is called, another `flushTaskQueue` call could occur,
13301335 // so the safety lock must go after the check.
13311336 while (comp.link_task_queue.check()) |tasks| {
13321337 comp.link_task_queue_safety.lock();
13331338 defer comp.link_task_queue_safety.unlock();
1339
1340 if (comp.remaining_prelink_tasks > 0) {
1341 comp.link_task_queue_postponed.ensureUnusedCapacity(comp.gpa, tasks.len) catch |err| switch (err) {
1342 error.OutOfMemory => return diags.setAllocFailure(),
1343 };
1344 }
1345
13341346 for (tasks) |task| doTask(comp, tid, task);
1347
1348 if (comp.remaining_prelink_tasks == 0) {
1349 if (comp.bin_file) |base| if (!base.post_prelink) {
1350 base.prelink(comp.work_queue_progress_node) catch |err| switch (err) {
1351 error.OutOfMemory => diags.setAllocFailure(),
1352 error.LinkFailure => continue,
1353 };
1354 base.post_prelink = true;
1355 for (comp.link_task_queue_postponed.items) |task| doTask(comp, tid, task);
1356 comp.link_task_queue_postponed.clearRetainingCapacity();
1357 };
1358 }
13351359 }
13361360}
13371361
......@@ -1375,6 +1399,7 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
13751399 const diags = &comp.link_diags;
13761400 switch (task) {
13771401 .load_explicitly_provided => if (comp.bin_file) |base| {
1402 comp.remaining_prelink_tasks -= 1;
13781403 const prog_node = comp.work_queue_progress_node.start("Parse Linker Inputs", comp.link_inputs.len);
13791404 defer prog_node.end();
13801405 for (comp.link_inputs) |input| {
......@@ -1392,6 +1417,7 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
13921417 }
13931418 },
13941419 .load_host_libc => if (comp.bin_file) |base| {
1420 comp.remaining_prelink_tasks -= 1;
13951421 const prog_node = comp.work_queue_progress_node.start("Linker Parse Host libc", 0);
13961422 defer prog_node.end();
13971423
......@@ -1451,6 +1477,7 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
14511477 }
14521478 },
14531479 .load_object => |path| if (comp.bin_file) |base| {
1480 comp.remaining_prelink_tasks -= 1;
14541481 const prog_node = comp.work_queue_progress_node.start("Linker Parse Object", 0);
14551482 defer prog_node.end();
14561483 base.openLoadObject(path) catch |err| switch (err) {
......@@ -1459,6 +1486,7 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
14591486 };
14601487 },
14611488 .load_archive => |path| if (comp.bin_file) |base| {
1489 comp.remaining_prelink_tasks -= 1;
14621490 const prog_node = comp.work_queue_progress_node.start("Linker Parse Archive", 0);
14631491 defer prog_node.end();
14641492 base.openLoadArchive(path, null) catch |err| switch (err) {
......@@ -1467,6 +1495,7 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
14671495 };
14681496 },
14691497 .load_dso => |path| if (comp.bin_file) |base| {
1498 comp.remaining_prelink_tasks -= 1;
14701499 const prog_node = comp.work_queue_progress_node.start("Linker Parse Shared Library", 0);
14711500 defer prog_node.end();
14721501 base.openLoadDso(path, .{
......@@ -1478,6 +1507,7 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
14781507 };
14791508 },
14801509 .load_input => |input| if (comp.bin_file) |base| {
1510 comp.remaining_prelink_tasks -= 1;
14811511 const prog_node = comp.work_queue_progress_node.start("Linker Parse Input", 0);
14821512 defer prog_node.end();
14831513 base.loadInput(input) catch |err| switch (err) {
......@@ -1492,26 +1522,38 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
14921522 };
14931523 },
14941524 .codegen_nav => |nav_index| {
1495 const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
1496 defer pt.deactivate();
1497 pt.linkerUpdateNav(nav_index) catch |err| switch (err) {
1498 error.OutOfMemory => diags.setAllocFailure(),
1499 };
1525 if (comp.remaining_prelink_tasks == 0) {
1526 const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
1527 defer pt.deactivate();
1528 pt.linkerUpdateNav(nav_index) catch |err| switch (err) {
1529 error.OutOfMemory => diags.setAllocFailure(),
1530 };
1531 } else {
1532 comp.link_task_queue_postponed.appendAssumeCapacity(task);
1533 }
15001534 },
15011535 .codegen_func => |func| {
1502 const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
1503 defer pt.deactivate();
1504 // This call takes ownership of `func.air`.
1505 pt.linkerUpdateFunc(func.func, func.air) catch |err| switch (err) {
1506 error.OutOfMemory => diags.setAllocFailure(),
1507 };
1536 if (comp.remaining_prelink_tasks == 0) {
1537 const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
1538 defer pt.deactivate();
1539 // This call takes ownership of `func.air`.
1540 pt.linkerUpdateFunc(func.func, func.air) catch |err| switch (err) {
1541 error.OutOfMemory => diags.setAllocFailure(),
1542 };
1543 } else {
1544 comp.link_task_queue_postponed.appendAssumeCapacity(task);
1545 }
15081546 },
15091547 .codegen_type => |ty| {
1510 const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
1511 defer pt.deactivate();
1512 pt.linkerUpdateContainerType(ty) catch |err| switch (err) {
1513 error.OutOfMemory => diags.setAllocFailure(),
1514 };
1548 if (comp.remaining_prelink_tasks == 0) {
1549 const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
1550 defer pt.deactivate();
1551 pt.linkerUpdateContainerType(ty) catch |err| switch (err) {
1552 error.OutOfMemory => diags.setAllocFailure(),
1553 };
1554 } else {
1555 comp.link_task_queue_postponed.appendAssumeCapacity(task);
1556 }
15151557 },
15161558 .update_line_number => |ti| {
15171559 const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
src/link/Wasm/Flush.zig+4-2
......@@ -571,8 +571,10 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
571571 .__tls_size => @panic("TODO"),
572572 .object_global => |i| {
573573 const global = i.ptr(wasm);
574 try binary_writer.writeByte(@intFromEnum(@as(std.wasm.Valtype, global.flags.global_type.valtype.to())));
575 try binary_writer.writeByte(@intFromBool(global.flags.global_type.mutable));
574 try binary_bytes.appendSlice(gpa, &.{
575 @intFromEnum(@as(std.wasm.Valtype, global.flags.global_type.valtype.to())),
576 @intFromBool(global.flags.global_type.mutable),
577 });
576578 try emitExpr(wasm, binary_bytes, global.expr);
577579 },
578580 .nav_exe => @panic("TODO"),