lib/tldr/src/incremental/format.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const incremental = @import("root.zig");
4 const parallel = @import("../root.zig").parallel;
5
6 const Allocator = std.mem.Allocator;
7 const ContributionKind = incremental.ContributionKind;
8 const ContributionRecord = incremental.ContributionRecord;
9 const DiscardReason = incremental.DiscardReason;
10 const DiscardedContributionRecord = incremental.DiscardedContributionRecord;
11 const ArchiveMemberRecord = incremental.ArchiveMemberRecord;
12 const GotEntryRecord = incremental.GotEntryRecord;
13 const MergePieceRecord = incremental.MergePieceRecord;
14 const ExternalTargetRecord = incremental.ExternalTargetRecord;
15 const InputRecord = incremental.InputRecord;
16 const Manifest = incremental.Manifest;
17 const SectionRecord = incremental.SectionRecord;
18 const StringSpan = incremental.StringSpan;
19 const Strings = incremental.Strings;
20
21 const manifest_binary_magic = [_]u8{ 'T', 'L', 'D', 'R', 'I', 'M', 'F', 10 };
22
23 const file_endianness: u8 = switch (builtin.cpu.arch.endian()) {
24 .little => 0,
25 .big => 1,
26 };
27
28 const LinkMapTotals = struct {
29 retained_size: u64 = 0,
30 retained_reserve: u64 = 0,
31 discarded_size: u64 = 0,
32 };
33
34 const BinaryReader = struct {
35 bytes: []const u8,
36 index: usize = 0,
37
38 fn take(self: *BinaryReader, len: usize) ![]const u8 {
39 if (self.index > self.bytes.len or len > self.bytes.len - self.index) return error.InvalidManifestBinary;
40 const result = self.bytes[self.index..][0..len];
41 self.index += len;
42 return result;
43 }
44
45 fn readU8(self: *BinaryReader) !u8 {
46 return (try self.take(1))[0];
47 }
48
49 fn readU16(self: *BinaryReader) !u16 {
50 return std.mem.readInt(u16, (try self.take(2))[0..2], .little);
51 }
52
53 fn readU32(self: *BinaryReader) !u32 {
54 return std.mem.readInt(u32, (try self.take(4))[0..4], .little);
55 }
56
57 fn readU64(self: *BinaryReader) !u64 {
58 return std.mem.readInt(u64, (try self.take(8))[0..8], .little);
59 }
60
61 fn readBool(self: *BinaryReader) !bool {
62 return switch (try self.readU8()) {
63 0 => false,
64 1 => true,
65 else => error.InvalidManifestBinary,
66 };
67 }
68
69 fn readEnum(self: *BinaryReader, comptime Enum: type) !Enum {
70 const value = try self.readU8();
71 inline for (
72 @typeInfo(Enum).@"enum".field_names,
73 @typeInfo(Enum).@"enum".field_values,
74 ) |field_name, field_name_value| {
75 const field = .{ .name = field_name, .value = field_name_value };
76 if (field.value == value) return @fromBackingInt(@intCast(field.value));
77 }
78 return error.InvalidManifestBinary;
79 }
80
81 fn finish(self: BinaryReader) !void {
82 if (self.index != self.bytes.len) return error.InvalidManifestBinary;
83 }
84 };
85
86 const LinkMapContributionSummary = struct {
87 count: usize = 0,
88 size: u64 = 0,
89 reserved_size: u64 = 0,
90 };
91
92 const LinkMapDiscardSummary = struct {
93 count: usize = 0,
94 size: u64 = 0,
95 };
96
97 const link_map_discard_reasons = [_]DiscardReason{
98 .discarded,
99 .identical_code_folded,
100 };
101
102 pub fn textAlloc(manifest: Manifest, allocator: Allocator) ![]u8 {
103 var out = std.Io.Writer.Allocating.init(allocator);
104 defer out.deinit();
105 try writeText(manifest, &out.writer);
106 return try out.toOwnedSlice();
107 }
108
109 pub fn binaryAlloc(manifest: Manifest, allocator: Allocator) ![]u8 {
110 var out = std.Io.Writer.Allocating.init(allocator);
111 defer out.deinit();
112 try writeBinary(manifest, &out.writer);
113 return try out.toOwnedSlice();
114 }
115
116 pub fn manifestFromBinary(allocator: Allocator, bytes: []u8) !Manifest {
117 const walked = try walkBinary(bytes);
118
119 const manifest: Manifest = .{
120 .target = .{
121 .object_format = try enumFromByte(@TypeOf(Manifest.empty().target.object_format), walked.header.object_format),
122 .architecture = try enumFromByte(@TypeOf(Manifest.empty().target.architecture), walked.header.architecture),
123 .endianness = try enumFromByte(@TypeOf(Manifest.empty().target.endianness), walked.header.endianness),
124 .pointer_width_bits = walked.header.pointer_width_bits,
125 },
126 .output_kind = try enumFromByte(@TypeOf(Manifest.empty().output_kind), walked.header.output_kind),
127 .entry_symbol_id = walked.header.entry_symbol_id,
128 .image_base = walked.header.image_base,
129 .page_size = walked.header.page_size,
130 .gc_sections = try boolFromByte(walked.header.gc_sections),
131 .icf = try enumFromByte(@TypeOf(Manifest.empty().icf), walked.header.icf),
132 .strip_debug = try boolFromByte(walked.header.strip_debug),
133 .build_id = try enumFromByte(@TypeOf(Manifest.empty().build_id), walked.header.build_id),
134 .input_hashes_recorded = try boolFromByte(walked.header.input_hashes_recorded),
135 .inputs_read_at_ns = @bitCast(walked.header.inputs_read_at_ns),
136 .strings = .{
137 .spans = walked.spans,
138 .blob = walked.blob,
139 },
140 .inputs = recordSlice(InputRecord, bytes, walked.sections[0]),
141 .sections = recordSlice(SectionRecord, bytes, walked.sections[1]),
142 .contributions = recordSlice(ContributionRecord, bytes, walked.sections[2]),
143 .discarded_contributions = recordSlice(DiscardedContributionRecord, bytes, walked.sections[3]),
144 .external_targets = recordSlice(ExternalTargetRecord, bytes, walked.sections[4]),
145 .archive_members = recordSlice(ArchiveMemberRecord, bytes, walked.sections[5]),
146 .got_entries = recordSlice(GotEntryRecord, bytes, walked.sections[6]),
147 .merge_pieces = recordSlice(MergePieceRecord, bytes, walked.sections[7]),
148 };
149
150 const string_count = std.math.cast(u32, walked.spans.len) orelse return error.InvalidManifestBinary;
151 if (string_count != 0 and manifest.entry_symbol_id >= string_count) return error.InvalidManifestBinary;
152 for (walked.spans) |span| {
153 if (span.offset > walked.blob.len or span.len > walked.blob.len - span.offset) {
154 return error.InvalidManifestBinary;
155 }
156 }
157
158 try validateRecords(allocator, bytes, walked.sections, string_count);
159 return manifest;
160 }
161
162 const binary_header_scalar_bytes = 1 + 1 + 1 + 2 + 1 + 4 + 8 + 8 + 1 + 1 + 1 + 1 + 1 + 8;
163 const header_read_at_patch_offset = binary_header_scalar_bytes - 8;
164
165 const BinaryHeader = struct {
166 object_format: u8,
167 architecture: u8,
168 endianness: u8,
169 pointer_width_bits: u16,
170 output_kind: u8,
171 entry_symbol_id: u32,
172 image_base: u64,
173 page_size: u64,
174 gc_sections: u8,
175 icf: u8,
176 strip_debug: u8,
177 build_id: u8,
178 input_hashes_recorded: u8,
179 inputs_read_at_ns: u64,
180 };
181
182 const WalkedBinary = struct {
183 header_offset: usize,
184 header: BinaryHeader,
185 spans: []const StringSpan,
186 blob: []const u8,
187 sections: [record_section_count]RecordSectionLayout,
188 };
189
190 fn walkBinary(bytes: []const u8) !WalkedBinary {
191 var reader: BinaryReader = .{ .bytes = bytes };
192 const magic = try reader.take(manifest_binary_magic.len);
193 if (!std.mem.eql(u8, magic, &manifest_binary_magic)) return error.UnsupportedManifestProtocol;
194 const protocol_len = std.math.cast(usize, try reader.readU32()) orelse return error.InvalidManifestBinary;
195 const protocol = try reader.take(protocol_len);
196 if (!std.mem.eql(u8, protocol, incremental.manifest_binary_protocol)) return error.UnsupportedManifestProtocol;
197 if (try reader.readU8() != file_endianness) return error.UnsupportedManifestProtocol;
198
199 const header_offset = reader.index;
200 const header = BinaryHeader{
201 .object_format = try reader.readU8(),
202 .architecture = try reader.readU8(),
203 .endianness = try reader.readU8(),
204 .pointer_width_bits = try reader.readU16(),
205 .output_kind = try reader.readU8(),
206 .entry_symbol_id = try reader.readU32(),
207 .image_base = try reader.readU64(),
208 .page_size = try reader.readU64(),
209 .gc_sections = try reader.readU8(),
210 .icf = try reader.readU8(),
211 .strip_debug = try reader.readU8(),
212 .build_id = try reader.readU8(),
213 .input_hashes_recorded = try reader.readU8(),
214 .inputs_read_at_ns = try reader.readU64(),
215 };
216
217 const string_count = std.math.cast(usize, try reader.readU32()) orelse return error.InvalidManifestBinary;
218 const spans_bytes = try reader.take(string_count * @sizeOf(StringSpan));
219 const spans = std.mem.bytesAsSlice(StringSpan, spans_bytes);
220 const blob_len = std.math.cast(usize, try reader.readU32()) orelse return error.InvalidManifestBinary;
221 const blob = try reader.take(blob_len);
222
223 var sections: [record_section_count]RecordSectionLayout = undefined;
224 for (§ions, 0..) |*section, section_index| {
225 const count = std.math.cast(usize, try reader.readU32()) orelse return error.InvalidManifestBinary;
226 const span = std.math.mul(usize, count, record_binary_sizes[section_index]) catch return error.InvalidManifestBinary;
227 section.* = .{ .count = count, .offset = reader.index };
228 _ = try reader.take(span);
229 }
230 try reader.finish();
231
232 return .{
233 .header_offset = header_offset,
234 .header = header,
235 .spans = spans,
236 .blob = blob,
237 .sections = sections,
238 };
239 }
240
241 fn recordSlice(comptime Record: type, bytes: []u8, layout: RecordSectionLayout) []Record {
242 if (layout.count == 0) return &.{};
243 return std.mem.bytesAsSlice(Record, bytes[layout.offset..][0 .. layout.count * @sizeOf(Record)]);
244 }
245
246 fn enumFromByte(comptime Enum: type, byte: u8) !Enum {
247 inline for (std.meta.tags(Enum)) |value| {
248 if (@backingInt(value) == byte) return value;
249 }
250 return error.InvalidManifestBinary;
251 }
252
253 fn boolFromByte(byte: u8) !bool {
254 return switch (byte) {
255 0 => false,
256 1 => true,
257 else => error.InvalidManifestBinary,
258 };
259 }
260
261 const record_section_count = 8;
262
263 pub const record_binary_sizes = [record_section_count]usize{
264 @sizeOf(InputRecord),
265 @sizeOf(SectionRecord),
266 @sizeOf(ContributionRecord),
267 @sizeOf(DiscardedContributionRecord),
268 @sizeOf(ExternalTargetRecord),
269 @sizeOf(ArchiveMemberRecord),
270 @sizeOf(GotEntryRecord),
271 @sizeOf(MergePieceRecord),
272 };
273
274 const record_chunk_len = 65536;
275
276 const RecordSectionLayout = struct {
277 count: usize,
278 offset: usize,
279 };
280
281 const ValidateTask = struct {
282 section: usize,
283 start: usize,
284 len: usize,
285 };
286
287 const ValidateFailure = struct {
288 section: usize = std.math.maxInt(usize),
289 start: usize = 0,
290 err: ?anyerror = null,
291
292 fn found(self: ValidateFailure) bool {
293 return self.err != null;
294 }
295
296 fn before(self: ValidateFailure, other: ValidateFailure) bool {
297 if (self.section != other.section) return self.section < other.section;
298 return self.start < other.start;
299 }
300 };
301
302 const ValidateFailures = parallel.FailureSlots(ValidateFailure);
303
304 const ValidateContext = struct {
305 bytes: []const u8,
306 sections: [record_section_count]RecordSectionLayout,
307 string_count: u32,
308 tasks: []const ValidateTask = &.{},
309 failures: ?*ValidateFailures = null,
310 };
311
312 fn validateRecords(
313 allocator: Allocator,
314 bytes: []const u8,
315 sections: [record_section_count]RecordSectionLayout,
316 string_count: u32,
317 ) !void {
318 var context = ValidateContext{
319 .bytes = bytes,
320 .sections = sections,
321 .string_count = string_count,
322 };
323
324 var total_records: usize = 0;
325 for (sections) |section| total_records += section.count;
326
327 if (total_records < record_chunk_len or !parallel.overlapAvailable()) {
328 for (sections, 0..) |section, section_index| {
329 try validateRecordRange(&context, section_index, 0, section.count);
330 }
331 return;
332 }
333
334 var tasks = std.ArrayListUnmanaged(ValidateTask).empty;
335 defer tasks.deinit(allocator);
336 for (sections, 0..) |section, section_index| {
337 var start: usize = 0;
338 while (start < section.count) {
339 const len = @min(record_chunk_len, section.count - start);
340 try tasks.append(allocator, .{ .section = section_index, .start = start, .len = len });
341 start += len;
342 }
343 }
344 if (tasks.items.len == 0) return;
345
346 const workers = parallel.chooseWorkers(tasks.items.len, 0);
347 var failures = try ValidateFailures.init(allocator, workers, .{});
348 defer failures.deinit(allocator);
349 context.tasks = tasks.items;
350 context.failures = &failures;
351 parallel.forItems(tasks.items.len, workers, &context, validateTaskItem);
352 if (failures.earliest(ValidateFailure.found, ValidateFailure.before)) |failure| {
353 return failure.err.?;
354 }
355 }
356
357 fn validateTaskItem(context: *ValidateContext, worker: usize, task_index: usize) void {
358 const task = context.tasks[task_index];
359 validateRecordRange(context, task.section, task.start, task.len) catch |err| {
360 const failures = context.failures.?;
361 const current = failures.items[worker];
362 const failure = ValidateFailure{ .section = task.section, .start = task.start, .err = err };
363 if (!current.found() or ValidateFailure.before(failure, current)) {
364 failures.record(worker, failure);
365 }
366 };
367 }
368
369 const FieldCheck = struct {
370 offset: usize,
371 kind: enum { string_id, boolean, contribution_kind, discard_reason },
372 };
373
374 const section_field_checks = [record_section_count][]const FieldCheck{
375 &.{
376 .{ .offset = @offsetOf(InputRecord, "name_id"), .kind = .string_id },
377 .{ .offset = @offsetOf(InputRecord, "identity_recorded"), .kind = .boolean },
378 },
379 &.{
380 .{ .offset = @offsetOf(SectionRecord, "name_id"), .kind = .string_id },
381 },
382 &.{
383 .{ .offset = @offsetOf(ContributionRecord, "input_name_id"), .kind = .string_id },
384 .{ .offset = @offsetOf(ContributionRecord, "name_id"), .kind = .string_id },
385 .{ .offset = @offsetOf(ContributionRecord, "output_section_name_id"), .kind = .string_id },
386 .{ .offset = @offsetOf(ContributionRecord, "kind"), .kind = .contribution_kind },
387 },
388 &.{
389 .{ .offset = @offsetOf(DiscardedContributionRecord, "input_name_id"), .kind = .string_id },
390 .{ .offset = @offsetOf(DiscardedContributionRecord, "name_id"), .kind = .string_id },
391 .{ .offset = @offsetOf(DiscardedContributionRecord, "reason"), .kind = .discard_reason },
392 },
393 &.{
394 .{ .offset = @offsetOf(ExternalTargetRecord, "name_id"), .kind = .string_id },
395 .{ .offset = @offsetOf(ExternalTargetRecord, "address_signed"), .kind = .boolean },
396 },
397 &.{
398 .{ .offset = @offsetOf(ArchiveMemberRecord, "name_id"), .kind = .string_id },
399 .{ .offset = @offsetOf(ArchiveMemberRecord, "selected"), .kind = .boolean },
400 },
401 &.{
402 .{ .offset = @offsetOf(GotEntryRecord, "input_name_id"), .kind = .string_id },
403 .{ .offset = @offsetOf(GotEntryRecord, "name_id"), .kind = .string_id },
404 },
405 &.{
406 .{ .offset = @offsetOf(MergePieceRecord, "input_name_id"), .kind = .string_id },
407 },
408 };
409
410 fn validateRecordRange(context: *ValidateContext, section_index: usize, start: usize, len: usize) !void {
411 const layout = context.sections[section_index];
412 const record_size = record_binary_sizes[section_index];
413 const checks = section_field_checks[section_index];
414 var index = start;
415 while (index < start + len) : (index += 1) {
416 const record_bytes = context.bytes[layout.offset + index * record_size ..][0..record_size];
417 for (checks) |check| {
418 switch (check.kind) {
419 .string_id => {
420 const id = std.mem.readInt(u32, record_bytes[check.offset..][0..4], .little);
421 if (id >= context.string_count) return error.InvalidManifestBinary;
422 },
423 .boolean => _ = try boolFromByte(record_bytes[check.offset]),
424 .contribution_kind => _ = try enumFromByte(ContributionKind, record_bytes[check.offset]),
425 .discard_reason => _ = try enumFromByte(DiscardReason, record_bytes[check.offset]),
426 }
427 }
428 }
429 }
430
431 pub const ManifestBinaryLayout = struct {
432 header_offset: usize,
433 sections: [record_section_count]RecordSectionLayout,
434 };
435
436 pub fn recordSectionLayouts(bytes: []const u8) !ManifestBinaryLayout {
437 const walked = try walkBinary(bytes);
438 return .{ .header_offset = walked.header_offset, .sections = walked.sections };
439 }
440
441 pub const manifest_field_patch_capacity = 40;
442
443 pub const ManifestFieldPatch = struct {
444 offset: usize,
445 len: usize,
446 bytes: [manifest_field_patch_capacity]u8,
447
448 pub fn slice(self: *const ManifestFieldPatch) []const u8 {
449 return self.bytes[0..self.len];
450 }
451 };
452
453 const input_scalar_patch_offset = @offsetOf(InputRecord, "size");
454 const input_scalar_patch_len = @offsetOf(InputRecord, "identity_recorded") + 1 - input_scalar_patch_offset;
455 const contribution_scalar_patch_offset = @offsetOf(ContributionRecord, "size");
456 const contribution_scalar_patch_len = @offsetOf(ContributionRecord, "file_size") + 8 - contribution_scalar_patch_offset;
457 const archive_member_scalar_patch_offset = @offsetOf(ArchiveMemberRecord, "hash");
458 const archive_member_scalar_patch_len = @offsetOf(ArchiveMemberRecord, "link_hash") + 8 - archive_member_scalar_patch_offset;
459
460 pub fn manifestScalarPatchesAlloc(
461 allocator: Allocator,
462 encoded: []const u8,
463 manifest: Manifest,
464 input_indexes: []const usize,
465 contribution_indexes: []const usize,
466 archive_member_indexes: []const usize,
467 ) ![]ManifestFieldPatch {
468 const layout = try recordSectionLayouts(encoded);
469 const sections = layout.sections;
470 const patches = try allocator.alloc(
471 ManifestFieldPatch,
472 1 + input_indexes.len + contribution_indexes.len + archive_member_indexes.len,
473 );
474 errdefer allocator.free(patches);
475 var cursor: usize = 0;
476 var read_at_patch = ManifestFieldPatch{
477 .offset = layout.header_offset + header_read_at_patch_offset,
478 .len = 8,
479 .bytes = undefined,
480 };
481 std.mem.writeInt(u64, read_at_patch.bytes[0..8], @bitCast(manifest.inputs_read_at_ns), .little);
482 patches[cursor] = read_at_patch;
483 cursor += 1;
484 for (input_indexes) |index| {
485 if (index >= sections[0].count or index >= manifest.inputs.len) return error.InvalidManifestBinary;
486 patches[cursor] = recordBytesPatch(
487 InputRecord,
488 &manifest.inputs[index],
489 sections[0].offset + index * record_binary_sizes[0],
490 input_scalar_patch_offset,
491 input_scalar_patch_len,
492 );
493 cursor += 1;
494 }
495 for (contribution_indexes) |index| {
496 if (index >= sections[2].count or index >= manifest.contributions.len) return error.InvalidManifestBinary;
497 patches[cursor] = recordBytesPatch(
498 ContributionRecord,
499 &manifest.contributions[index],
500 sections[2].offset + index * record_binary_sizes[2],
501 contribution_scalar_patch_offset,
502 contribution_scalar_patch_len,
503 );
504 cursor += 1;
505 }
506 for (archive_member_indexes) |index| {
507 if (index >= sections[5].count or index >= manifest.archive_members.len) return error.InvalidManifestBinary;
508 patches[cursor] = recordBytesPatch(
509 ArchiveMemberRecord,
510 &manifest.archive_members[index],
511 sections[5].offset + index * record_binary_sizes[5],
512 archive_member_scalar_patch_offset,
513 archive_member_scalar_patch_len,
514 );
515 cursor += 1;
516 }
517 return patches;
518 }
519
520 fn recordBytesPatch(
521 comptime Record: type,
522 record: *const Record,
523 record_offset: usize,
524 field_offset: usize,
525 len: usize,
526 ) ManifestFieldPatch {
527 var patch = ManifestFieldPatch{
528 .offset = record_offset + field_offset,
529 .len = len,
530 .bytes = undefined,
531 };
532 const source = std.mem.asBytes(record)[field_offset..][0..len];
533 @memcpy(patch.bytes[0..len], source);
534 return patch;
535 }
536
537 pub fn writeBinary(manifest: Manifest, writer: *std.Io.Writer) !void {
538 try writer.writeAll(&manifest_binary_magic);
539 try writeStringBinary(writer, incremental.manifest_binary_protocol);
540 try writer.writeByte(file_endianness);
541
542 try writeEnumBinary(writer, manifest.target.object_format);
543 try writeEnumBinary(writer, manifest.target.architecture);
544 try writeEnumBinary(writer, manifest.target.endianness);
545 try writeU16Binary(writer, manifest.target.pointer_width_bits);
546 try writeEnumBinary(writer, manifest.output_kind);
547 try writeU32Binary(writer, manifest.entry_symbol_id);
548 try writeU64Binary(writer, manifest.image_base);
549 try writeU64Binary(writer, manifest.page_size);
550 try writeBoolBinary(writer, manifest.gc_sections);
551 try writeEnumBinary(writer, manifest.icf);
552 try writeBoolBinary(writer, manifest.strip_debug);
553 try writeEnumBinary(writer, manifest.build_id);
554 try writeBoolBinary(writer, manifest.input_hashes_recorded);
555 try writeU64Binary(writer, @bitCast(manifest.inputs_read_at_ns));
556
557 const strings = &manifest.strings;
558 try writeCountBinary(writer, strings.count());
559 const tier0_blob_len = std.math.cast(u32, strings.blob.len) orelse return error.ManifestTooLarge;
560 for (strings.spans) |span| {
561 try writeU32Binary(writer, span.offset);
562 try writeU32Binary(writer, span.len);
563 }
564 for (strings.appendix_spans.items) |span| {
565 const shifted = std.math.add(u32, span.offset, tier0_blob_len) catch return error.ManifestTooLarge;
566 try writeU32Binary(writer, shifted);
567 try writeU32Binary(writer, span.len);
568 }
569 const total_blob = std.math.add(usize, strings.blob.len, strings.appendix.items.len) catch return error.ManifestTooLarge;
570 try writeCountBinary(writer, total_blob);
571 try writer.writeAll(strings.blob);
572 try writer.writeAll(strings.appendix.items);
573
574 try writeRecordSection(writer, manifest.inputs);
575 try writeRecordSection(writer, manifest.sections);
576 try writeRecordSection(writer, manifest.contributions);
577 try writeRecordSection(writer, manifest.discarded_contributions);
578 try writeRecordSection(writer, manifest.external_targets);
579 try writeRecordSection(writer, manifest.archive_members);
580 try writeRecordSection(writer, manifest.got_entries);
581 try writeRecordSection(writer, manifest.merge_pieces);
582 }
583
584 fn writeRecordSection(writer: *std.Io.Writer, records: anytype) !void {
585 try writeCountBinary(writer, records.len);
586 if (records.len != 0) {
587 try writer.writeAll(std.mem.sliceAsBytes(records));
588 }
589 }
590
591 pub fn writeText(manifest: Manifest, writer: *std.Io.Writer) std.Io.Writer.Error!void {
592 const totals = linkMapTotals(manifest);
593
594 try writer.writeAll("tldr link map\n");
595 try writer.print(
596 "target: {s}/{s} {s} ptr={d}\n",
597 .{
598 @tagName(manifest.target.object_format),
599 @tagName(manifest.target.architecture),
600 @tagName(manifest.target.endianness),
601 manifest.target.pointer_width_bits,
602 },
603 );
604 try writer.print(
605 "output: {s} entry={s} image_base=0x{x} page_size=0x{x}\n",
606 .{ @tagName(manifest.output_kind), manifest.entrySymbol(), manifest.image_base, manifest.page_size },
607 );
608 try writer.print("options: gc_sections={} icf={s} strip_debug={} build_id={s}\n", .{ manifest.gc_sections, @tagName(manifest.icf), manifest.strip_debug, @tagName(manifest.build_id) });
609 try writer.print(
610 "summary: inputs={d} sections={d} retained={d} retained_size={d} retained_reserve={d} discarded={d} discarded_size={d} external_targets={d} archive_members={d} got_entries={d} merge_pieces={d}\n",
611 .{
612 manifest.inputs.len,
613 manifest.sections.len,
614 manifest.contributions.len,
615 totals.retained_size,
616 totals.retained_reserve,
617 manifest.discarded_contributions.len,
618 totals.discarded_size,
619 manifest.external_targets.len,
620 manifest.archive_members.len,
621 manifest.got_entries.len,
622 manifest.merge_pieces.len,
623 },
624 );
625 if (manifest.contributions.len != 0) {
626 try writer.writeAll("retained by output:\n");
627 for (manifest.sections) |section| {
628 const summary = outputContributionSummary(manifest, section.name_id);
629 if (summary.count == 0) continue;
630 try writer.print(
631 " {s} count={d} size={d} reserve={d}\n",
632 .{ manifest.string(section.name_id), summary.count, summary.size, summary.reserved_size },
633 );
634 }
635 }
636 if (manifest.discarded_contributions.len != 0) {
637 try writer.writeAll("discarded by reason:\n");
638 for (link_map_discard_reasons) |reason| {
639 const summary = discardedReasonSummary(manifest, reason);
640 if (summary.count == 0) continue;
641 try writer.print(
642 " {s} count={d} size={d}\n",
643 .{ @tagName(reason), summary.count, summary.size },
644 );
645 }
646 }
647
648 try writer.print("inputs ({d}):\n", .{manifest.inputs.len});
649 for (manifest.inputs) |input| {
650 if (manifest.input_hashes_recorded) {
651 try writer.print(" {s} size={d} hash=0x{x}\n", .{ manifest.string(input.name_id), input.size, input.hash });
652 } else {
653 try writer.print(" {s} size={d} hash=unrecorded\n", .{ manifest.string(input.name_id), input.size });
654 }
655 }
656
657 try writer.print("sections ({d}):\n", .{manifest.sections.len});
658 for (manifest.sections) |section| {
659 try writer.print(
660 " {s} addr=0x{x} file=0x{x} size={d} reserve={d} align={d}\n",
661 .{
662 manifest.string(section.name_id),
663 section.address,
664 section.file_offset,
665 section.size,
666 section.reserved_size,
667 section.alignment,
668 },
669 );
670 }
671
672 try writer.print("contributions ({d}):\n", .{manifest.contributions.len});
673 for (manifest.contributions) |contribution| {
674 try writer.print(
675 " {s} <- {s}:{s}#{d} kind={s} addr=0x{x} file=0x{x} size={d} file_size={d} reserve={d} align={d}\n",
676 .{
677 manifest.string(contribution.output_section_name_id),
678 manifest.string(contribution.input_name_id),
679 manifest.string(contribution.name_id),
680 contribution.ordinal,
681 @tagName(contribution.kind),
682 contribution.address,
683 contribution.file_offset,
684 contribution.size,
685 contribution.file_size,
686 contribution.reserved_size,
687 contribution.alignment,
688 },
689 );
690 }
691
692 try writer.print("discarded ({d}):\n", .{manifest.discarded_contributions.len});
693 for (manifest.discarded_contributions) |contribution| {
694 try writer.print(
695 " {s}:{s}#{d} reason={s} size={d} align={d}\n",
696 .{
697 manifest.string(contribution.input_name_id),
698 manifest.string(contribution.name_id),
699 contribution.ordinal,
700 @tagName(contribution.reason),
701 contribution.size,
702 contribution.alignment,
703 },
704 );
705 }
706 }
707
708 fn linkMapTotals(manifest: Manifest) LinkMapTotals {
709 var totals: LinkMapTotals = .{};
710 for (manifest.contributions) |contribution| {
711 totals.retained_size += contribution.size;
712 totals.retained_reserve += contribution.reserved_size;
713 }
714 for (manifest.discarded_contributions) |contribution| {
715 totals.discarded_size += contribution.size;
716 }
717 return totals;
718 }
719
720 fn outputContributionSummary(manifest: Manifest, output_section_name_id: u32) LinkMapContributionSummary {
721 var summary: LinkMapContributionSummary = .{};
722 const output_section_name = manifest.string(output_section_name_id);
723 for (manifest.contributions) |contribution| {
724 if (contribution.output_section_name_id != output_section_name_id and
725 !std.mem.eql(u8, manifest.string(contribution.output_section_name_id), output_section_name)) continue;
726 summary.count += 1;
727 summary.size += contribution.size;
728 summary.reserved_size += contribution.reserved_size;
729 }
730 return summary;
731 }
732
733 fn discardedReasonSummary(manifest: Manifest, reason: DiscardReason) LinkMapDiscardSummary {
734 var summary: LinkMapDiscardSummary = .{};
735 for (manifest.discarded_contributions) |contribution| {
736 if (contribution.reason != reason) continue;
737 summary.count += 1;
738 summary.size += contribution.size;
739 }
740 return summary;
741 }
742
743 fn writeBoolBinary(writer: *std.Io.Writer, value: bool) !void {
744 try writer.writeByte(if (value) 1 else 0);
745 }
746
747 fn writeEnumBinary(writer: *std.Io.Writer, value: anytype) !void {
748 const byte = std.math.cast(u8, @backingInt(value)) orelse return error.ManifestTooLarge;
749 try writer.writeByte(byte);
750 }
751
752 fn writeCountBinary(writer: *std.Io.Writer, value: usize) !void {
753 const count = std.math.cast(u32, value) orelse return error.ManifestTooLarge;
754 try writeU32Binary(writer, count);
755 }
756
757 fn writeStringBinary(writer: *std.Io.Writer, value: []const u8) !void {
758 try writeCountBinary(writer, value.len);
759 try writer.writeAll(value);
760 }
761
762 fn writeU16Binary(writer: *std.Io.Writer, value: u16) !void {
763 var bytes: [2]u8 = undefined;
764 std.mem.writeInt(u16, &bytes, value, .little);
765 try writer.writeAll(&bytes);
766 }
767
768 fn writeU32Binary(writer: *std.Io.Writer, value: u32) !void {
769 var bytes: [4]u8 = undefined;
770 std.mem.writeInt(u32, &bytes, value, .little);
771 try writer.writeAll(&bytes);
772 }
773
774 fn writeU64Binary(writer: *std.Io.Writer, value: u64) !void {
775 var bytes: [8]u8 = undefined;
776 std.mem.writeInt(u64, &bytes, value, .little);
777 try writer.writeAll(&bytes);
778 }