authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-07 15:32:02-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:35-07:00
log8a8bf5ad023451a22fd7abf438e6c7ee105ac6bf
tree9053ae0292046c50120f690c28774c8a715d7a5f
parentaffe5ed867009c889f2df7b624387c2bceefcc0e

maker: update ObjCopy to new system


6 files changed, 215 insertions(+), 114 deletions(-)

BRANCH_TODO+1
......@@ -20,6 +20,7 @@
2020 - and adjust dependencyInner to not openDir()
2121
2222## Followup Issues
23* stop leaking into global process arena
2324* reduce the size of Maker.Step.Extended (make Run smaller) probably by using an arena per make
2425* link_eh_frame_hdr should be DefaultingBool
2526* make --foo, --no-foo CLI args uniform (make them -f args instead)
lib/compiler/Maker.zig+2-3
......@@ -612,7 +612,7 @@ pub fn main(init: process.Init.Minimal) !void {
612612 step.state = .precheck_done;
613613 const deps = step_index.ptr(c).deps.slice(c);
614614 step.pending_deps = @intCast(deps.len);
615 step.reset(gpa);
615 step.reset(&maker);
616616 }
617617 continue :rebuild;
618618 },
......@@ -1506,10 +1506,9 @@ fn constructGraphAndCheckForDependencyLoop(
15061506/// invalidated.
15071507pub fn invalidateResult(maker: *Maker, step: *Step) bool {
15081508 if (step.state == .precheck_done) return false;
1509 const gpa = maker.gpa;
15101509 assert(step.pending_deps == 0);
15111510 step.state = .precheck_done;
1512 step.reset(gpa);
1511 step.reset(maker);
15131512 for (step.dependants.items) |dependant_index| {
15141513 const dependant = maker.stepByIndex(dependant_index);
15151514 _ = invalidateResult(maker, dependant);
lib/compiler/Maker/Step.zig+6-4
......@@ -23,6 +23,7 @@ pub const Run = @import("Step/Run.zig");
2323pub const InstallArtifact = @import("Step/InstallArtifact.zig");
2424pub const InstallFile = @import("Step/InstallFile.zig");
2525pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
26pub const ObjCopy = @import("Step/ObjCopy.zig");
2627
2728/// Avoid false sharing.
2829_: void align(std.atomic.cache_line) = {},
......@@ -76,7 +77,7 @@ pub const Extended = union(enum) {
7677 install_artifact: InstallArtifact,
7778 install_dir: Todo,
7879 install_file: InstallFile,
79 obj_copy: Todo,
80 obj_copy: ObjCopy,
8081 options: Todo,
8182 remove_dir: Todo,
8283 run: Run,
......@@ -306,8 +307,9 @@ pub fn make(
306307}
307308
308309/// Prepares the step for being re-evaluated.
309pub fn reset(step: *Step, gpa: Allocator) void {
310pub fn reset(step: *Step, maker: *Maker) void {
310311 assert(step.state == .precheck_done);
312 const gpa = maker.gpa;
311313
312314 if (step.result_failed_command) |cmd| gpa.free(cmd);
313315
......@@ -318,7 +320,7 @@ pub fn reset(step: *Step, gpa: Allocator) void {
318320 step.result_peak_rss = 0;
319321 step.result_failed_command = null;
320322 step.test_results = .{};
321 step.clearWatchInputs();
323 step.clearWatchInputs(maker);
322324
323325 step.result_error_bundle.deinit(gpa);
324326 step.result_error_bundle = std.zig.ErrorBundle.empty;
......@@ -732,7 +734,7 @@ fn failWithCacheError(
732734pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
733735 if (s.test_results.isSuccess()) {
734736 man.writeManifest() catch |err| {
735 try s.addError(maker, "unable to write cache manifest: {t}", .{err});
737 try s.addError(maker, "failed writing cache manifest: {t}", .{err});
736738 };
737739 }
738740}
lib/compiler/Maker/Step/ObjCopy.zig+101-68
......@@ -2,6 +2,7 @@ const ObjCopy = @This();
22
33const std = @import("std");
44const Io = std.Io;
5const Path = std.Build.Cache.Path;
56const allocPrint = std.fmt.allocPrint;
67const Configuration = std.Build.Configuration;
78
......@@ -23,120 +24,152 @@ pub fn make(
2324 const conf_step = step_index.ptr(conf);
2425 const conf_oc = conf_step.extended.get(conf.extra).obj_copy;
2526 const cache_root = graph.local_cache_root;
27 const input_lazy_path = conf_oc.input_file.get(conf);
28 const only_section: ?[]const u8 = if (conf_oc.only_section.value) |s| s.slice(conf) else null;
29 const opt_basename: ?[]const u8 = if (conf_oc.basename.value) |s| s.slice(conf) else null;
30 const opt_debug_basename: ?[]const u8 = if (conf_oc.debug_basename.value) |s| s.slice(conf) else null;
2631
27 try step.singleUnchangingWatchInput(maker, arena, conf_oc.input_file);
32 try step.singleUnchangingWatchInput(maker, arena, input_lazy_path);
2833
2934 var man = graph.cache.obtain();
3035 defer man.deinit();
3136
32 const src_path = try maker.resolveLazyPathIndex(arena, conf_oc.input_file, step_index);
33 _ = try man.addFilePath(src_path, null);
34 man.hash.addOptionalBytes(conf_oc.only_section);
35 man.hash.addOptional(conf_oc.pad_to);
36 man.hash.addOptional(conf_oc.format);
37 man.hash.add(conf_oc.compress_debug);
38 man.hash.add(conf_oc.strip);
39 man.hash.add(conf_oc.output_file_debug != null);
37 const input_path = try maker.resolveLazyPath(arena, input_lazy_path, step_index);
38 _ = try man.addFilePath(input_path, null);
39 man.hash.addOptionalBytes(only_section);
40 man.hash.addOptionalBytes(opt_basename);
41 man.hash.addOptionalBytes(opt_debug_basename);
42 man.hash.addOptional(conf_oc.pad_to.value);
43 man.hash.add(conf_oc.flags.format);
44 man.hash.add(conf_oc.flags.compress_debug);
45 man.hash.add(conf_oc.flags.strip);
46 man.hash.add(conf_oc.debug_file.value != null);
4047
41 if (try step.cacheHit(&man)) {
48 const basename = opt_basename orelse Io.Dir.path.basename(input_path.sub_path);
49
50 if (try step.cacheHit(maker, &man)) {
4251 // Cache hit, skip subprocess execution.
4352 const digest = man.final();
44 conf_oc.output_file.path = try cache_root.join(arena, &.{
45 "o", &digest, conf_oc.basename,
46 });
47 if (conf_oc.output_file_debug) |*file| {
48 file.path = try cache_root.join(arena, &.{
49 "o", &digest, try allocPrint(arena, "{s}.debug", .{conf_oc.basename}),
53 maker.generatedPath(conf_oc.output_file).* = .{
54 .root_dir = cache_root,
55 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
56 };
57 if (conf_oc.debug_file.value) |debug_file| {
58 const debug_basename = opt_debug_basename orelse try allocPrint(arena, "{s}.debug", .{
59 Io.Dir.path.basename(input_path.sub_path),
5060 });
61 maker.generatedPath(debug_file).* = .{
62 .root_dir = cache_root,
63 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, debug_basename }),
64 };
5165 }
5266 return;
5367 }
5468
69 // We don't find out more input files while executing objcopy so we can
70 // already obtain the digest and use it directly as the output path.
5571 const digest = man.final();
56 const cache_path = "o" ++ Io.Dir.path.sep_str ++ digest;
57 const full_dest_path = try cache_root.join(arena, &.{ cache_path, conf_oc.basename });
58 const full_dest_path_debug = try cache_root.join(arena, &.{
59 cache_path, try allocPrint(arena, "{s}.debug", .{conf_oc.basename}),
60 });
61 cache_root.handle.createDirPath(io, cache_path) catch |err|
62 return step.fail("unable to make path {s}: {t}", .{ cache_path, err });
72 const dest_path: Path = .{
73 .root_dir = cache_root,
74 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }),
75 };
76 const dest_dirname = dest_path.dirname().?;
77 dest_dirname.root_dir.handle.createDirPath(io, dest_dirname.sub_path) catch |err|
78 return step.fail(maker, "failed to create path {f}: {t}", .{ dest_dirname, err });
6379
6480 var argv: std.ArrayList([]const u8) = .empty;
6581 try argv.ensureUnusedCapacity(arena, 11);
6682
6783 argv.addManyAsArrayAssumeCapacity(2).* = .{ graph.zig_exe, "objcopy" };
6884
69 if (conf_oc.only_section) |only_section|
70 argv.addManyAsArrayAssumeCapacity(2).* = .{ "-j", only_section };
85 if (only_section) |s| argv.addManyAsArrayAssumeCapacity(2).* = .{ "-j", s };
7186
72 switch (conf_oc.strip) {
87 switch (conf_oc.flags.strip) {
7388 .none => {},
7489 .debug => argv.appendAssumeCapacity("--strip-debug"),
7590 .debug_and_symbols => argv.appendAssumeCapacity("--strip-all"),
7691 }
7792
78 if (conf_oc.pad_to) |pad_to| {
93 if (conf_oc.pad_to.value) |pad_to| {
7994 argv.addManyAsArrayAssumeCapacity(2).* = .{
8095 "--pad-to", try allocPrint(arena, "{d}", .{pad_to}),
8196 };
8297 }
8398
84 if (conf_oc.format) |format| {
85 argv.addManyAsArrayAssumeCapacity(2).* = .{
86 "-O",
87 switch (format) {
88 .bin => "binary",
89 .hex => "hex",
90 .elf => "elf",
91 },
92 };
99 switch (conf_oc.flags.format) {
100 .default => {},
101 else => |t| argv.addManyAsArrayAssumeCapacity(2).* = .{ "-O", @tagName(t) },
93102 }
94103
95 if (conf_oc.compress_debug)
104 if (conf_oc.flags.compress_debug)
96105 argv.appendAssumeCapacity("--compress-debug-sections");
97106
98 if (conf_oc.output_file_debug != null)
99 argv.appendAssumeCapacity(try allocPrint(arena, "--extract-to={s}", .{full_dest_path_debug}));
107 if (conf_oc.debug_file.value) |debug_file| {
108 const debug_basename = opt_debug_basename orelse try allocPrint(arena, "{s}.debug", .{
109 Io.Dir.path.basename(input_path.sub_path),
110 });
111 const debug_dest_path: Path = .{
112 .root_dir = cache_root,
113 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, debug_basename }),
114 };
115 argv.appendAssumeCapacity(try allocPrint(arena, "--extract-to={f}", .{debug_dest_path}));
116 maker.generatedPath(debug_file).* = debug_dest_path;
117 }
100118
101 try argv.ensureUnusedCapacity(arena, 9);
119 try argv.ensureUnusedCapacity(arena, conf_oc.add_section.slice.len * 2);
102120
103 if (conf_oc.add_section) |section| {
121 for (conf_oc.add_section.slice) |section| {
104122 argv.appendAssumeCapacity("--add-section");
105123 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={f}", .{
106 section.section_name, try maker.resolveLazyPathIndex(arena, section.file_path, step_index),
124 section.section_name.slice(conf),
125 try maker.resolveLazyPathIndex(arena, section.file_path, step_index),
107126 }));
108127 }
109128
110 if (conf_oc.set_section_alignment) |set_align| {
111 argv.appendAssumeCapacity("--set-section-alignment");
112 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={d}", .{ set_align.section_name, set_align.alignment }));
113 }
129 for (conf_oc.update_section.slice) |update| {
130 const name = update.section_name.slice(conf);
114131
115 if (conf_oc.set_section_flags) |set_flags| {
116 const f = set_flags.flags;
117 // trailing comma is allowed
118 argv.appendAssumeCapacity("--set-section-flags");
119 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{
120 set_flags.section_name,
121 if (f.alloc) "alloc," else "",
122 if (f.contents) "contents," else "",
123 if (f.load) "load," else "",
124 if (f.readonly) "readonly," else "",
125 if (f.code) "code," else "",
126 if (f.exclude) "exclude," else "",
127 if (f.large) "large," else "",
128 if (f.merge) "merge," else "",
129 if (f.strings) "strings," else "",
130 }));
132 try argv.ensureUnusedCapacity(arena, 4);
133
134 if (update.flags.alignment.toBytes()) |a| {
135 argv.appendAssumeCapacity("--set-section-alignment");
136 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={d}", .{ name, a }));
137 }
138
139 const f = update.flags.section_flags;
140 const default_flags: Configuration.Step.ObjCopy.SectionFlags = .{};
141
142 if (f != default_flags) {
143 // trailing comma is allowed
144 argv.appendAssumeCapacity("--set-section-flags");
145 argv.appendAssumeCapacity(try allocPrint(arena, "{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{
146 name,
147 if (f.alloc) "alloc," else "",
148 if (f.contents) "contents," else "",
149 if (f.load) "load," else "",
150 if (f.readonly) "readonly," else "",
151 if (f.code) "code," else "",
152 if (f.exclude) "exclude," else "",
153 if (f.large) "large," else "",
154 if (f.merge) "merge," else "",
155 if (f.strings) "strings," else "",
156 }));
157 }
131158 }
132159
133 argv.appendAssumeCapacity(src_path);
134 argv.appendAssumeCapacity(full_dest_path);
160 argv.appendAssumeCapacity(try allocPrint(arena, "{f}", .{input_path}));
161 argv.appendAssumeCapacity(try allocPrint(arena, "{f}", .{dest_path}));
135162
136163 argv.appendAssumeCapacity("--listen=-");
137 _ = try Step.evalZigProcess(step_index, maker, argv.items, progress_node, false);
164 _ = Step.evalZigProcess(step_index, maker, argv.items, progress_node, false) catch |err| switch (err) {
165 error.NeedCompileErrorCheck => unreachable,
166 else => |e| return e,
167 };
168
169 maker.generatedPath(conf_oc.output_file).* = dest_path;
138170
139 conf_oc.output_file.path = full_dest_path;
140 if (conf_oc.output_file_debug) |*file| file.path = full_dest_path_debug;
141 try man.writeManifest();
171 man.writeManifest() catch |err| switch (err) {
172 error.Canceled => |e| return e,
173 else => |e| try step.addError(maker, "failed writing cache manifest: {t}", .{e}),
174 };
142175}
lib/std/Build/Configuration.zig+98-1
......@@ -1102,10 +1102,87 @@ pub const Step = extern struct {
11021102
11031103 pub const ObjCopy = struct {
11041104 flags: @This().Flags,
1105 input_file: LazyPath.Index,
1106 output_file: GeneratedFileIndex,
1107 basename: Storage.FlagOptional(.flags, .basename, String),
1108 debug_file: Storage.FlagOptional(.flags, .debug_file, GeneratedFileIndex),
1109 debug_basename: Storage.FlagOptional(.flags, .debug_basename, String),
1110 only_section: Storage.FlagOptional(.flags, .only_section, String),
1111 pad_to: Storage.FlagOptional(.flags, .pad_to, u64),
1112 add_section: Storage.FlagLengthPrefixedList(.flags, .add_section, AddSection),
1113 update_section: Storage.FlagLengthPrefixedList(.flags, .update_section, UpdateSection),
1114
1115 pub const Format = enum(u2) {
1116 binary,
1117 hex,
1118 elf,
1119 default,
1120
1121 pub fn init(f: ?std.Build.Step.ObjCopy.Format) @This() {
1122 return switch (f orelse return .default) {
1123 .binary => .binary,
1124 .hex => .hex,
1125 .elf => .elf,
1126 };
1127 }
1128 };
1129
1130 pub const Strip = enum(u2) {
1131 none,
1132 debug,
1133 debug_and_symbols,
1134 };
1135
1136 pub const AddSection = extern struct {
1137 section_name: String,
1138 file_path: LazyPath.Index,
1139 };
1140
1141 pub const UpdateSection = extern struct {
1142 section_name: String,
1143 flags: @This().Flags,
1144
1145 pub const Flags = packed struct(u32) {
1146 section_flags: SectionFlags,
1147 alignment: Alignment,
1148 _: u17 = 0,
1149 };
1150 };
1151
1152 pub const SectionFlags = packed struct(u9) {
1153 /// add SHF_ALLOC
1154 alloc: bool = false,
1155 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing
1156 contents: bool = false,
1157 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents)
1158 load: bool = false,
1159 /// readonly: clear default SHF_WRITE flag
1160 readonly: bool = false,
1161 /// add SHF_EXECINSTR
1162 code: bool = false,
1163 /// add SHF_EXCLUDE
1164 exclude: bool = false,
1165 /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64
1166 large: bool = false,
1167 /// add SHF_MERGE
1168 merge: bool = false,
1169 /// add SHF_STRINGS
1170 strings: bool = false,
1171 };
11051172
11061173 pub const Flags = packed struct(u32) {
11071174 tag: Tag = .obj_copy,
1108 _: u27 = 0,
1175 basename: bool,
1176 debug_file: bool,
1177 debug_basename: bool,
1178 format: Format,
1179 strip: Strip,
1180 compress_debug: bool,
1181 only_section: bool,
1182 pad_to: bool,
1183 add_section: bool,
1184 update_section: bool,
1185 _: u15 = 0,
11091186 };
11101187 };
11111188
......@@ -1658,6 +1735,26 @@ pub const Bytes = extern struct {
16581735 }
16591736};
16601737
1738/// Stored as a power-of-two, with one special value to indicate none.
1739pub const Alignment = enum(u6) {
1740 @"1" = 0,
1741 @"2" = 1,
1742 @"4" = 2,
1743 @"8" = 3,
1744 @"16" = 4,
1745 @"32" = 5,
1746 @"64" = 6,
1747 none = std.math.maxInt(u6),
1748 _,
1749
1750 pub fn toBytes(a: @This()) ?u64 {
1751 return switch (a) {
1752 .none => null,
1753 else => @as(u64, 1) << @intFromEnum(a),
1754 };
1755 }
1756};
1757
16611758pub const DefaultingBool = enum(u2) {
16621759 false,
16631760 true,
lib/std/Build/Step/ObjCopy.zig+7-38
......@@ -6,11 +6,11 @@ const Configuration = std.Build.Configuration;
66
77step: Step,
88input_file: std.Build.LazyPath,
9basename: []const u8,
9basename: ?[]const u8,
1010output_file: Configuration.GeneratedFileIndex,
1111output_file_debug: Configuration.OptionalGeneratedFileIndex,
1212
13format: ?RawFormat,
13format: ?Format,
1414only_section: ?[]const u8,
1515pad_to: ?u64,
1616strip: Strip,
......@@ -22,38 +22,9 @@ set_section_flags: ?SetSectionFlags,
2222
2323pub const base_tag: Step.Tag = .obj_copy;
2424
25pub const RawFormat = enum {
26 bin,
27 hex,
28 elf,
29};
30
31pub const Strip = enum {
32 none,
33 debug,
34 debug_and_symbols,
35};
36
37pub const SectionFlags = packed struct {
38 /// add SHF_ALLOC
39 alloc: bool = false,
40 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing
41 contents: bool = false,
42 /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents)
43 load: bool = false,
44 /// readonly: clear default SHF_WRITE flag
45 readonly: bool = false,
46 /// add SHF_EXECINSTR
47 code: bool = false,
48 /// add SHF_EXCLUDE
49 exclude: bool = false,
50 /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64
51 large: bool = false,
52 /// add SHF_MERGE
53 merge: bool = false,
54 /// add SHF_STRINGS
55 strings: bool = false,
56};
25pub const Format = enum { binary, hex, elf };
26pub const Strip = Configuration.Step.ObjCopy.Strip;
27pub const SectionFlags = Configuration.Step.ObjCopy.SectionFlags;
5728
5829pub const AddSection = struct {
5930 section_name: []const u8,
......@@ -72,7 +43,7 @@ pub const SetSectionFlags = struct {
7243
7344pub const Options = struct {
7445 basename: ?[]const u8 = null,
75 format: ?RawFormat = null,
46 format: ?Format = null,
7647 only_section: ?[]const u8 = null,
7748 pad_to: ?u64 = null,
7849
......@@ -95,7 +66,6 @@ pub fn create(
9566 options: Options,
9667) *ObjCopy {
9768 const graph = owner.graph;
98 const arena = graph.arena;
9969 const obj_copy = graph.create(ObjCopy);
10070 obj_copy.* = .{
10171 .step = .init(.{
......@@ -104,8 +74,7 @@ pub fn create(
10474 .owner = owner,
10575 }),
10676 .input_file = input_file,
107 .basename = options.basename orelse
108 std.fmt.allocPrint(arena, "{f}", .{input_file.fmt(graph)}) catch @panic("OOM"),
77 .basename = options.basename,
10978 .output_file = graph.addGeneratedFile(&obj_copy.step),
11079 .output_file_debug = if (options.strip != .none and options.extract_to_separate_file)
11180 .init(graph.addGeneratedFile(&obj_copy.step))