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 }