lib/tldr/src/model.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const ObjectFormat = enum {
4 elf,
5 coff,
6 macho,
7 wasm,
8 };
9
10 pub const Architecture = enum {
11 unknown,
12 x86_64,
13 aarch64,
14 riscv64,
15 wasm32,
16 };
17
18 pub const Endianness = enum {
19 little,
20 big,
21 };
22
23 pub const OutputKind = enum {
24 executable,
25 relocatable,
26 shared_library,
27 };
28
29 pub const IncrementalMode = enum {
30 off,
31 prepare,
32 relink,
33 };
34
35 pub const IcfMode = enum {
36 off,
37 all,
38 };
39
40 pub const BuildIdMode = enum {
41 none,
42 fast,
43 sha1,
44 };
45
46 pub const ProductStage = enum {
47 input_discovery,
48 archive_member_selection,
49 archive_table_parse,
50 archive_definition_indexing,
51 archive_summary_parse,
52 archive_symbol_recording,
53 selected_member_parse,
54 symbol_database,
55 section_contribution_graph,
56 contribution_ehframe_scan,
57 contribution_classification,
58 contribution_merge_registration,
59 contribution_chain_assignment,
60 contribution_string_merge,
61 duplicate_policy,
62 layout_groups,
63 address_assignment,
64 relaxation,
65 relocation_application,
66 output_writing,
67 write_symbol_table,
68 write_zero_fill,
69 payload_materialization,
70 write_metadata,
71 relink_manifest_decode,
72 relink_change_detection,
73 relink_direct_evidence,
74 relink_output_io,
75 relink_patch_apply,
76 diagnostics,
77 manifest_recording,
78
79 pub fn label(self: ProductStage) []const u8 {
80 return switch (self) {
81 .input_discovery => "input discovery",
82 .archive_member_selection => "archive member selection",
83 .archive_table_parse => "archive table parse",
84 .archive_definition_indexing => "archive definition indexing",
85 .archive_summary_parse => "archive summary parse",
86 .archive_symbol_recording => "archive symbol recording",
87 .selected_member_parse => "selected member parse",
88 .symbol_database => "symbol database",
89 .section_contribution_graph => "section contribution graph",
90 .contribution_ehframe_scan => "contribution eh_frame scan",
91 .contribution_classification => "contribution classification",
92 .contribution_merge_registration => "contribution merge registration",
93 .contribution_chain_assignment => "contribution chain assignment",
94 .contribution_string_merge => "contribution string merge",
95 .duplicate_policy => "duplicate policy",
96 .layout_groups => "layout groups",
97 .address_assignment => "address assignment",
98 .relaxation => "relaxation",
99 .relocation_application => "relocation application",
100 .output_writing => "output writing",
101 .write_symbol_table => "write symbol table",
102 .write_zero_fill => "write zero fill",
103 .payload_materialization => "payload materialization",
104 .write_metadata => "write metadata",
105 .relink_manifest_decode => "relink manifest decode",
106 .relink_change_detection => "relink change detection",
107 .relink_direct_evidence => "relink direct evidence",
108 .relink_output_io => "relink output io",
109 .relink_patch_apply => "relink patch apply",
110 .diagnostics => "diagnostics",
111 .manifest_recording => "manifest recording",
112 };
113 }
114
115 pub fn traceName(comptime self: ProductStage) []const u8 {
116 return switch (self) {
117 .input_discovery => "parse",
118 .archive_member_selection => "archive",
119 .archive_table_parse => "table",
120 .archive_definition_indexing => "indexing",
121 .archive_summary_parse => "summary",
122 .archive_symbol_recording => "recording",
123 .selected_member_parse => "selected",
124 .symbol_database => "symbol",
125 .section_contribution_graph => "contribution",
126 .contribution_ehframe_scan => "ehscan",
127 .contribution_classification => "classify",
128 .contribution_merge_registration => "register",
129 .contribution_chain_assignment => "chains",
130 .contribution_string_merge => "strings",
131 .duplicate_policy => "duplicate",
132 .layout_groups => "layout",
133 .address_assignment => "address",
134 .relaxation => "relaxation",
135 .relocation_application => "relocation",
136 .output_writing => "write",
137 .write_symbol_table => "symtab",
138 .write_zero_fill => "zerofill",
139 .payload_materialization => "materialize",
140 .write_metadata => "metadata",
141 .relink_manifest_decode => "manifest",
142 .relink_change_detection => "changes",
143 .relink_direct_evidence => "evidence",
144 .relink_output_io => "outputio",
145 .relink_patch_apply => "patch",
146 .diagnostics => "diagnostics",
147 .manifest_recording => "manifest",
148 };
149 }
150 };
151
152 pub const product_stage_order = [_]ProductStage{
153 .input_discovery,
154 .archive_member_selection,
155 .archive_table_parse,
156 .archive_definition_indexing,
157 .archive_summary_parse,
158 .archive_symbol_recording,
159 .selected_member_parse,
160 .symbol_database,
161 .section_contribution_graph,
162 .contribution_ehframe_scan,
163 .contribution_classification,
164 .contribution_merge_registration,
165 .contribution_chain_assignment,
166 .contribution_string_merge,
167 .duplicate_policy,
168 .layout_groups,
169 .address_assignment,
170 .relaxation,
171 .relocation_application,
172 .output_writing,
173 .write_symbol_table,
174 .write_zero_fill,
175 .payload_materialization,
176 .write_metadata,
177 .relink_manifest_decode,
178 .relink_change_detection,
179 .relink_direct_evidence,
180 .relink_output_io,
181 .relink_patch_apply,
182 .diagnostics,
183 .manifest_recording,
184 };
185
186 pub const Target = struct {
187 object_format: ObjectFormat = .elf,
188 architecture: Architecture = .x86_64,
189 endianness: Endianness = .little,
190 pointer_width_bits: u16 = 64,
191
192 pub const linux_x86_64_elf: Target = .{
193 .object_format = .elf,
194 .architecture = .x86_64,
195 .endianness = .little,
196 .pointer_width_bits = 64,
197 };
198
199 pub const windows_x86_64_coff: Target = .{
200 .object_format = .coff,
201 .architecture = .x86_64,
202 .endianness = .little,
203 .pointer_width_bits = 64,
204 };
205
206 pub const macos_x86_64_macho: Target = .{
207 .object_format = .macho,
208 .architecture = .x86_64,
209 .endianness = .little,
210 .pointer_width_bits = 64,
211 };
212 };
213
214 pub const LinkCommitObserver = struct {
215 context: *anyopaque,
216 committed: *const fn (context: *anyopaque) void,
217
218 pub fn notify(self: LinkCommitObserver) void {
219 self.committed(self.context);
220 }
221 };
222
223 pub const LinkOptions = struct {
224 target: Target = .linux_x86_64_elf,
225 output_kind: OutputKind = .executable,
226 entry_symbol: []const u8 = "_start",
227 image_base: u64 = 0x400000,
228 page_size: u64 = 0x1000,
229 pie: bool = false,
230 dynamic_linker: ?[]const u8 = null,
231 soname: ?[]const u8 = null,
232 export_dynamic: bool = false,
233 eh_frame_header: bool = false,
234 incremental_mode: IncrementalMode = .prepare,
235 incremental_reserve_percent: u8 = 24,
236 inputs_read_at_ns: i64 = 0,
237 gc_sections: bool = false,
238 icf: IcfMode = .off,
239 strip_debug: bool = false,
240 build_id: BuildIdMode = .none,
241 max_link_jobs: usize = 0,
242 mapped_output_file: ?std.Io.File = null,
243 mapped_output_min_size: usize = 8 * 1024 * 1024,
244 diagnostics: ?*Diagnostics = null,
245 commit_observer: ?LinkCommitObserver = null,
246
247 pub fn requiresDynamicLinking(self: LinkOptions) bool {
248 return self.output_kind == .shared_library or
249 self.pie or
250 self.dynamic_linker != null or
251 self.soname != null or
252 self.export_dynamic;
253 }
254 };
255
256 pub const InputIdentity = struct {
257 mtime_ns: i64,
258 inode: u64,
259 };
260
261 pub const Input = struct {
262 name: []const u8,
263 bytes: []const u8,
264 identity: ?InputIdentity = null,
265 };
266
267 pub const Diagnostics = struct {
268 pub const max_recorded_undefined_symbols = 8;
269
270 first_undefined_symbol: ?UndefinedSymbol = null,
271 first_unsupported_relocation: ?UnsupportedRelocation = null,
272 first_unsupported_input_format: ?UnsupportedInputFormat = null,
273 undefined_symbols: [max_recorded_undefined_symbols]UndefinedSymbol = undefined,
274 undefined_symbol_count: usize = 0,
275
276 pub const UndefinedSymbol = struct {
277 name: []const u8,
278 input_name: []const u8,
279 };
280
281 pub const UnsupportedRelocation = struct {
282 input_name: []const u8,
283 section_name: []const u8,
284 symbol_name: []const u8,
285 relocation_type: u32,
286 };
287
288 pub const UnsupportedInputFormat = struct {
289 input_name: []const u8,
290 member_name: ?[]const u8 = null,
291 input_format: ObjectFormat,
292 target_format: ObjectFormat,
293 };
294
295 pub const UndefinedSymbols = struct {
296 symbols: []const UndefinedSymbol,
297 hidden_count: usize = 0,
298 };
299
300 pub const LinkFailure = union(enum) {
301 undefined_symbols: UndefinedSymbols,
302 undefined_symbol: UndefinedSymbol,
303 unsupported_relocation: UnsupportedRelocation,
304 unsupported_input_format: UnsupportedInputFormat,
305 generic: Error,
306 };
307
308 pub fn clear(self: *Diagnostics) void {
309 self.* = .{};
310 }
311
312 pub fn recordUndefinedSymbol(
313 self: *Diagnostics,
314 input_name: []const u8,
315 symbol_name: []const u8,
316 ) void {
317 if (self.undefined_symbol_count < self.undefined_symbols.len) {
318 self.undefined_symbols[self.undefined_symbol_count] = .{
319 .name = symbol_name,
320 .input_name = input_name,
321 };
322 }
323 self.undefined_symbol_count += 1;
324 if (self.first_undefined_symbol == null) {
325 self.first_undefined_symbol = .{
326 .name = symbol_name,
327 .input_name = input_name,
328 };
329 }
330 }
331
332 pub fn recordedUndefinedSymbols(self: *const Diagnostics) []const UndefinedSymbol {
333 return self.undefined_symbols[0..@min(self.undefined_symbol_count, self.undefined_symbols.len)];
334 }
335
336 pub fn recordUnsupportedRelocation(
337 self: *Diagnostics,
338 input_name: []const u8,
339 section_name: []const u8,
340 symbol_name: []const u8,
341 relocation_type: u32,
342 ) void {
343 if (self.first_unsupported_relocation == null) {
344 self.first_unsupported_relocation = .{
345 .input_name = input_name,
346 .section_name = section_name,
347 .symbol_name = symbol_name,
348 .relocation_type = relocation_type,
349 };
350 }
351 }
352
353 pub fn recordUnsupportedInputFormat(
354 self: *Diagnostics,
355 input_name: []const u8,
356 input_format: ObjectFormat,
357 target_format: ObjectFormat,
358 ) void {
359 if (self.first_unsupported_input_format == null) {
360 self.first_unsupported_input_format = .{
361 .input_name = input_name,
362 .input_format = input_format,
363 .target_format = target_format,
364 };
365 }
366 }
367
368 pub fn recordUnsupportedArchiveMemberFormat(
369 self: *Diagnostics,
370 input_name: []const u8,
371 member_name: []const u8,
372 input_format: ObjectFormat,
373 target_format: ObjectFormat,
374 ) void {
375 if (self.first_unsupported_input_format == null) {
376 self.first_unsupported_input_format = .{
377 .input_name = input_name,
378 .member_name = member_name,
379 .input_format = input_format,
380 .target_format = target_format,
381 };
382 }
383 }
384
385 pub fn linkFailure(self: *const Diagnostics, err: Error) LinkFailure {
386 if (err == error.UndefinedSymbol) {
387 const undefined_symbols = self.recordedUndefinedSymbols();
388 if (undefined_symbols.len > 1) {
389 return .{ .undefined_symbols = .{
390 .symbols = undefined_symbols,
391 .hidden_count = self.undefined_symbol_count - undefined_symbols.len,
392 } };
393 }
394 if (self.first_undefined_symbol) |undefined_symbol| {
395 return .{ .undefined_symbol = undefined_symbol };
396 }
397 }
398
399 if (err == error.UnsupportedRelocation) {
400 if (self.first_unsupported_relocation) |relocation| {
401 return .{ .unsupported_relocation = relocation };
402 }
403 }
404
405 if (err == error.UnsupportedFormat) {
406 if (self.first_unsupported_input_format) |unsupported| {
407 return .{ .unsupported_input_format = unsupported };
408 }
409 }
410
411 return .{ .generic = err };
412 }
413 };
414
415 pub const Error = std.mem.Allocator.Error || error{
416 DuplicateSymbol,
417 InvalidAlignment,
418 InvalidArchive,
419 InvalidElfHeader,
420 InvalidObject,
421 InvalidRange,
422 InvalidStringTable,
423 MissingEntrySymbol,
424 MissingSection,
425 MissingStringTable,
426 MissingSymbolTable,
427 NoAllocSections,
428 RelocationOverflow,
429 OutputFileError,
430 SectionLayoutOverflow,
431 TooManyProgramHeaders,
432 TooManySections,
433 UndefinedSymbol,
434 UnsupportedArchitecture,
435 UnsupportedDynamicLinking,
436 UnsupportedEhFrameHeader,
437 UnsupportedFormat,
438 UnsupportedOutputKind,
439 UnsupportedRelocation,
440 };
441
442 pub fn validateOptions(options: LinkOptions) Error!void {
443 if (options.page_size == 0 or !std.math.isPowerOfTwo(options.page_size)) {
444 return error.InvalidAlignment;
445 }
446 if (options.image_base % options.page_size != 0) {
447 return error.InvalidAlignment;
448 }
449 if (options.target.pointer_width_bits != 64) return error.UnsupportedArchitecture;
450 }
451
452 test "diagnostics report multiple undefined symbols" {
453 var diagnostics: Diagnostics = .{};
454 diagnostics.recordUndefinedSymbol("tiny.o", "__tiny_make_integer");
455 diagnostics.recordUndefinedSymbol("libtinyrt.a", "memcpy");
456
457 const failure = diagnostics.linkFailure(error.UndefinedSymbol);
458 const undefined_symbols = switch (failure) {
459 .undefined_symbols => |undefined_symbols| undefined_symbols,
460 else => return error.ExpectedUndefinedSymbolsDiagnostic,
461 };
462 try std.testing.expectEqual(@as(usize, 2), undefined_symbols.symbols.len);
463 try std.testing.expectEqual(@as(usize, 0), undefined_symbols.hidden_count);
464 try std.testing.expectEqualStrings("__tiny_make_integer", undefined_symbols.symbols[0].name);
465 try std.testing.expectEqualStrings("tiny.o", undefined_symbols.symbols[0].input_name);
466 try std.testing.expectEqualStrings("memcpy", undefined_symbols.symbols[1].name);
467 try std.testing.expectEqualStrings("libtinyrt.a", undefined_symbols.symbols[1].input_name);
468 }
469
470 test "product stages define shared labels and trace names" {
471 try std.testing.expectEqual(@as(usize, 31), product_stage_order.len);
472 try std.testing.expectEqual(ProductStage.input_discovery, product_stage_order[0]);
473 try std.testing.expectEqual(ProductStage.manifest_recording, product_stage_order[product_stage_order.len - 1]);
474 try std.testing.expectEqualStrings("section contribution graph", ProductStage.section_contribution_graph.label());
475 try std.testing.expectEqualStrings("contribution", ProductStage.section_contribution_graph.traceName());
476 try std.testing.expectEqualStrings("contribution string merge", ProductStage.contribution_string_merge.label());
477 try std.testing.expectEqualStrings("chains", ProductStage.contribution_chain_assignment.traceName());
478 try std.testing.expectEqualStrings("manifest", ProductStage.manifest_recording.traceName());
479 try std.testing.expectEqualStrings("archive summary parse", ProductStage.archive_summary_parse.label());
480 try std.testing.expectEqualStrings("recording", ProductStage.archive_symbol_recording.traceName());
481 try std.testing.expectEqualStrings("selected", ProductStage.selected_member_parse.traceName());
482 }
483
484 test "diagnostics report unsupported relocation details" {
485 var diagnostics: Diagnostics = .{};
486 diagnostics.recordUnsupportedRelocation("tiny.o", ".text", "__tiny_runtime_call", 42);
487
488 const failure = diagnostics.linkFailure(error.UnsupportedRelocation);
489 const relocation = switch (failure) {
490 .unsupported_relocation => |relocation| relocation,
491 else => return error.ExpectedUnsupportedRelocationDiagnostic,
492 };
493 try std.testing.expectEqual(@as(u32, 42), relocation.relocation_type);
494 try std.testing.expectEqualStrings("tiny.o", relocation.input_name);
495 try std.testing.expectEqualStrings(".text", relocation.section_name);
496 try std.testing.expectEqualStrings("__tiny_runtime_call", relocation.symbol_name);
497 }
498
499 test "diagnostics report unsupported input format details" {
500 var diagnostics: Diagnostics = .{};
501 diagnostics.recordUnsupportedInputFormat("main.obj", .coff, .elf);
502
503 const failure = diagnostics.linkFailure(error.UnsupportedFormat);
504 const unsupported = switch (failure) {
505 .unsupported_input_format => |unsupported| unsupported,
506 else => return error.ExpectedUnsupportedInputFormatDiagnostic,
507 };
508 try std.testing.expectEqual(ObjectFormat.coff, unsupported.input_format);
509 try std.testing.expectEqual(ObjectFormat.elf, unsupported.target_format);
510 try std.testing.expectEqualStrings("main.obj", unsupported.input_name);
511 }
512
513 test "diagnostics report unsupported archive member format details" {
514 var diagnostics: Diagnostics = .{};
515 diagnostics.recordUnsupportedArchiveMemberFormat("libmixed.a", "needed.obj", .coff, .elf);
516
517 const failure = diagnostics.linkFailure(error.UnsupportedFormat);
518 const unsupported = switch (failure) {
519 .unsupported_input_format => |unsupported| unsupported,
520 else => return error.ExpectedUnsupportedInputFormatDiagnostic,
521 };
522 try std.testing.expectEqual(ObjectFormat.coff, unsupported.input_format);
523 try std.testing.expectEqual(ObjectFormat.elf, unsupported.target_format);
524 try std.testing.expectEqualStrings("libmixed.a", unsupported.input_name);
525 try std.testing.expectEqualStrings("needed.obj", unsupported.member_name orelse return error.ExpectedUnsupportedInputFormatDiagnostic);
526 }