lib/tldr/src/formats/elf/layout/collect.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const allocators = @import("alloc");
  3 const root = @import("../../../root.zig");
  4 const elf = @import("../root.zig");
  5 
  6 const Allocator = std.mem.Allocator;
  7 const model = root.model;
  8 const parallel = root.parallel;
  9 const trace = root.trace;
 10 const ObjectFile = elf.parser.ObjectFile;
 11 const OutputSection = elf.layout.OutputSection;
 12 const SectionContribution = elf.layout.SectionContribution;
 13 const ObjectLayout = elf.layout.ObjectLayout;
 14 const SymbolRef = elf.layout.SymbolRef;
 15 const GlobalSymbol = elf.layout.GlobalSymbol;
 16 const OutputSectionKind = elf.output_section.OutputSectionKind;
 17 const SectionHeader = elf.format.SectionHeader;
 18 const Symbol = elf.format.Symbol;
 19 const output_section_count = elf.output_section.output_section_count;
 20 const debugOutputIndexForName = elf.output_section.debugOutputIndexForName;
 21 const outputIndexForAlloc = elf.output_section.indexForAllocatedNamed;
 22 const outputIndexForFixedMerge = elf.output_section.indexForAllocated;
 23 const isAllocPayloadSection = elf.output_section.isAllocatedPayloadType;
 24 const sectionIsAllocated = elf.format.sectionIsAllocated;
 25 const sectionNameOrEmpty = elf.parser.sectionNameOrEmpty;
 26 const validateAlignment = elf.format.validateAlignment;
 27 const alignForwardU64 = elf.format.alignForwardU64;
 28 const contributionReserveSize = elf.layout.contributionReserveSize;
 29 
 30 const parallel_section_threshold = 4096;
 31 
 32 const Class = enum(u8) {
 33     none,
 34     empty_alloc,
 35     payload,
 36     ehframe,
 37     fixed_merge,
 38     string_merge,
 39 };
 40 
 41 const Classification = struct {
 42     classes: []Class,
 43     outputs: []u8,
 44     merge_flags: []bool,
 45     object_bases: []usize,
 46 };
 47 
 48 const WalkFailure = struct {
 49     object_index: usize = std.math.maxInt(usize),
 50     section_index: usize = 0,
 51     err: ?model.Error = null,
 52 
 53     fn found(self: WalkFailure) bool {
 54         return self.err != null;
 55     }
 56 
 57     fn before(self: WalkFailure, other: WalkFailure) bool {
 58         if (self.object_index != other.object_index) return self.object_index < other.object_index;
 59         return self.section_index < other.section_index;
 60     }
 61 };
 62 
 63 const WalkFailures = parallel.FailureSlots(WalkFailure);
 64 
 65 pub fn sections(
 66     allocator: Allocator,
 67     objects: []const ObjectFile,
 68     output_sections: *[output_section_count]OutputSection,
 69     layouts: []ObjectLayout,
 70     section_contributions: []SectionContribution,
 71     layout_count: *usize,
 72     globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
 73     options: model.LinkOptions,
 74 ) model.Error!void {
 75     var string_merge: elf.merge.String = .{};
 76     defer string_merge.deinit(allocator);
 77     var eh_frame_scan: elf.ehframe.Scan = .{};
 78     defer eh_frame_scan.deinit(allocator);
 79 
 80     initObjectLayouts(objects, layouts, section_contributions);
 81     layout_count.* += objects.len;
 82 
 83     const classification = Classification{
 84         .classes = try allocator.alloc(Class, section_contributions.len),
 85         .outputs = try allocator.alloc(u8, section_contributions.len),
 86         .merge_flags = try allocator.alloc(bool, objects.len),
 87         .object_bases = try allocator.alloc(usize, objects.len),
 88     };
 89     defer {
 90         allocator.free(classification.object_bases);
 91         allocator.free(classification.merge_flags);
 92         allocator.free(classification.outputs);
 93         allocator.free(classification.classes);
 94     }
 95     var object_base: usize = 0;
 96     for (objects, 0..) |object, object_index| {
 97         classification.object_bases[object_index] = object_base;
 98         object_base += object.sections.len;
 99     }
100 
101     const classify_failure = run_classify: {
102         const classify_phase = trace.product(.contribution_classification);
103         defer classify_phase.end();
104         break :run_classify try classify(allocator, objects, layouts, classification, options);
105     };
106     {
107         const scan_phase = trace.product(.contribution_ehframe_scan);
108         defer scan_phase.end();
109         var eh_references: std.ArrayListUnmanaged(elf.ehframe.Scan.Reference) = .empty;
110         defer eh_references.deinit(allocator);
111         for (objects, 0..) |object, object_index| {
112             const base = classification.object_bases[object_index];
113             for (0..object.sections.len) |section_index| {
114                 if (classification.classes[base + section_index] != .ehframe) continue;
115                 try eh_references.append(allocator, .{
116                     .object_index = @intCast(object_index),
117                     .section_index = @intCast(section_index),
118                 });
119             }
120         }
121         try eh_frame_scan.prepare(allocator, objects, layouts, globals, options, eh_references.items);
122     }
123     const register_failure = run_register: {
124         const register_phase = trace.product(.contribution_merge_registration);
125         defer register_phase.end();
126         try reserveCommonSymbols(allocator, objects, layouts, globals);
127         try reserveMergeLayouts(allocator, objects, layouts, classification);
128         break :run_register try registerStringMerges(
129             allocator,
130             objects,
131             layouts,
132             classification,
133             output_sections,
134             &string_merge,
135         );
136     };
137     const assign_failure = run_assign: {
138         const assign_phase = trace.product(.contribution_chain_assignment);
139         defer assign_phase.end();
140         break :run_assign try assignChains(
141             allocator,
142             objects,
143             layouts,
144             classification,
145             eh_frame_scan.pending.items,
146             output_sections,
147             globals,
148             options,
149         );
150     };
151 
152     if (earliestWalkFailure(classify_failure, earliestWalkFailure(register_failure, assign_failure))) |failure| {
153         return failure.err.?;
154     }
155     const finish_phase = trace.product(.contribution_string_merge);
156     defer finish_phase.end();
157     try string_merge.finish(allocator, output_sections, options);
158 }
159 
160 fn earliestWalkFailure(left: ?WalkFailure, right: ?WalkFailure) ?WalkFailure {
161     const first = left orelse return right;
162     const second = right orelse return left;
163     return if (WalkFailure.before(first, second)) first else second;
164 }
165 
166 const ClassifyContext = struct {
167     objects: []const ObjectFile,
168     layouts: []ObjectLayout,
169     classification: Classification,
170     options: model.LinkOptions,
171     failures: *WalkFailures,
172 };
173 
174 fn classify(
175     allocator: Allocator,
176     objects: []const ObjectFile,
177     layouts: []ObjectLayout,
178     classification: Classification,
179     options: model.LinkOptions,
180 ) model.Error!?WalkFailure {
181     const requested_workers = if (options.max_link_jobs != 0) options.max_link_jobs else 0;
182     const workers = if (classification.classes.len >= parallel_section_threshold)
183         parallel.chooseWorkers(objects.len, requested_workers)
184     else
185         1;
186 
187     var failures = try WalkFailures.init(allocator, workers, .{});
188     defer failures.deinit(allocator);
189 
190     var context = ClassifyContext{
191         .objects = objects,
192         .layouts = layouts,
193         .classification = classification,
194         .options = options,
195         .failures = &failures,
196     };
197     if (workers <= 1) {
198         for (objects, 0..) |_, object_index| classifyObject(&context, 0, object_index);
199     } else {
200         parallel.forItems(objects.len, workers, &context, classifyObject);
201     }
202     return failures.earliest(WalkFailure.found, WalkFailure.before);
203 }
204 
205 fn classifyObject(context: *ClassifyContext, worker: usize, object_index: usize) void {
206     const object = context.objects[object_index];
207     const object_base = context.classification.object_bases[object_index];
208     const classes = context.classification.classes[object_base..][0..object.sections.len];
209     const outputs = context.classification.outputs[object_base..][0..object.sections.len];
210     const contributions = context.layouts[object_index].sections;
211     var has_merge = false;
212 
213     for (object.sections, 0..) |section, section_index| {
214         const class = classifySection(object, section, section_index, context.options) catch |err| {
215             recordWalkFailure(context.failures, worker, object_index, section_index, err);
216             classes[section_index] = .none;
217             outputs[section_index] = 0;
218             contributions[section_index] = .{};
219             continue;
220         };
221         classes[section_index] = class.class;
222         outputs[section_index] = class.output;
223         switch (class.class) {
224             .none, .fixed_merge, .string_merge => contributions[section_index] = .{},
225             .empty_alloc, .payload, .ehframe => {},
226         }
227         if (class.class == .fixed_merge or class.class == .string_merge) has_merge = true;
228     }
229     context.classification.merge_flags[object_index] = has_merge;
230 }
231 
232 fn recordWalkFailure(
233     failures: *WalkFailures,
234     worker: usize,
235     object_index: usize,
236     section_index: usize,
237     err: model.Error,
238 ) void {
239     const failure = WalkFailure{
240         .object_index = object_index,
241         .section_index = section_index,
242         .err = err,
243     };
244     const current = failures.items[worker];
245     if (!current.found() or WalkFailure.before(failure, current)) {
246         failures.record(worker, failure);
247     }
248 }
249 
250 const SectionClass = struct {
251     class: Class,
252     output: u8 = 0,
253 };
254 
255 fn classifySection(
256     object: ObjectFile,
257     section: SectionHeader,
258     section_index: usize,
259     options: model.LinkOptions,
260 ) model.Error!SectionClass {
261     const discarded_sections = object.discarded_sections;
262     if (discarded_sections.len != 0 and discarded_sections[section_index]) return .{ .class = .none };
263     const folded_sections = object.folded_sections;
264     if (folded_sections.len != 0 and folded_sections[section_index] != null) return .{ .class = .none };
265     if (section.size == 0) {
266         if (!sectionIsAllocated(section)) return .{ .class = .none };
267         if (!isAllocPayloadSection(section.section_type)) return error.UnsupportedFormat;
268         try validateAlignment(section.alignment);
269         const output_index = outputIndexForAlloc(section, sectionNameOrEmpty(object, section_index));
270         return .{ .class = .empty_alloc, .output = @intCast(output_index) };
271     }
272     if ((section.flags & std.elf.SHF_ALLOC) == 0) {
273         if (section.section_type != std.elf.SHT_PROGBITS) return .{ .class = .none };
274         const name = sectionNameOrEmpty(object, section_index);
275         const output_index = debugOutputIndexForName(name) orelse return .{ .class = .none };
276         if (options.strip_debug) return .{ .class = .none };
277         try validateAlignment(section.alignment);
278         return .{ .class = .payload, .output = @intCast(output_index) };
279     }
280     if (elf.ehframe.isSection(object, section_index, section) and object.relocationsForSection(section_index).len != 0) {
281         try validateAlignment(section.alignment);
282         return .{ .class = .ehframe, .output = @backingInt(OutputSectionKind.eh_frame) };
283     }
284     if (elf.merge.fixed(section)) {
285         if (object.relocationsForSection(section_index).len != 0) {
286             return classifyAllocPayload(object, section, section_index);
287         }
288         return .{ .class = .fixed_merge, .output = @intCast(outputIndexForFixedMerge(section)) };
289     }
290     if (elf.merge.string(section)) {
291         if (object.relocationsForSection(section_index).len != 0) {
292             return classifyAllocPayload(object, section, section_index);
293         }
294         return .{ .class = .string_merge };
295     }
296     return classifyAllocPayload(object, section, section_index);
297 }
298 
299 fn classifyAllocPayload(
300     object: ObjectFile,
301     section: SectionHeader,
302     section_index: usize,
303 ) model.Error!SectionClass {
304     if (!isAllocPayloadSection(section.section_type)) return error.UnsupportedFormat;
305     try validateAlignment(section.alignment);
306     const output_index = outputIndexForAlloc(section, sectionNameOrEmpty(object, section_index));
307     return .{ .class = .payload, .output = @intCast(output_index) };
308 }
309 
310 fn reserveCommonSymbols(
311     allocator: Allocator,
312     objects: []const ObjectFile,
313     layouts: []ObjectLayout,
314     globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
315 ) model.Error!void {
316     for (objects, 0..) |object, object_index| {
317         if (!object.has_common_symbols) continue;
318         for (object.symbols, 0..) |symbol, symbol_index| {
319             if (!commonSymbolEligible(object_index, symbol, symbol_index, globals)) continue;
320             _ = try ensureCommonSymbolLayouts(allocator, object, &layouts[object_index].common_symbols);
321             break;
322         }
323     }
324 }
325 
326 fn commonSymbolEligible(
327     object_index: usize,
328     symbol: Symbol,
329     symbol_index: usize,
330     globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
331 ) bool {
332     if (!symbol.isCommon()) return false;
333     if (symbol.name.len == 0) return true;
334     const global = globals.get(symbol.name) orelse return false;
335     const current_ref = SymbolRef{ .object_index = object_index, .symbol_index = symbol_index };
336     return global.ref.eql(current_ref);
337 }
338 
339 fn reserveMergeLayouts(
340     allocator: Allocator,
341     objects: []const ObjectFile,
342     layouts: []ObjectLayout,
343     classification: Classification,
344 ) model.Error!void {
345     for (objects, 0..) |object, object_index| {
346         if (!classification.merge_flags[object_index]) continue;
347         _ = try elf.merge.ensure(allocator, object, &layouts[object_index].merge_sections);
348     }
349 }
350 
351 fn registerStringMerges(
352     allocator: Allocator,
353     objects: []const ObjectFile,
354     layouts: []ObjectLayout,
355     classification: Classification,
356     output_sections: *[output_section_count]OutputSection,
357     string_merge: *elf.merge.String,
358 ) model.Error!?WalkFailure {
359     for (objects, 0..) |object, object_index| {
360         if (!classification.merge_flags[object_index]) continue;
361         const object_base = classification.object_bases[object_index];
362         const merge_sections = layouts[object_index].merge_sections;
363         var merge_starts: elf.merge.StartIndex = .{};
364         defer merge_starts.deinit(allocator);
365         var use_merge_start_index: ?bool = null;
366         for (object.sections, 0..) |section, section_index| {
367             if (classification.classes[object_base + section_index] != .string_merge) continue;
368             registerStringMerge(
369                 allocator,
370                 object,
371                 section,
372                 section_index,
373                 &merge_starts,
374                 &use_merge_start_index,
375                 output_sections,
376                 string_merge,
377                 merge_sections,
378             ) catch |err| {
379                 return WalkFailure{
380                     .object_index = object_index,
381                     .section_index = section_index,
382                     .err = err,
383                 };
384             };
385         }
386     }
387     return null;
388 }
389 
390 fn registerStringMerge(
391     allocator: Allocator,
392     object: ObjectFile,
393     section: SectionHeader,
394     section_index: usize,
395     merge_starts: *elf.merge.StartIndex,
396     use_merge_start_index: *?bool,
397     output_sections: *[output_section_count]OutputSection,
398     string_merge: *elf.merge.String,
399     merge_sections: []elf.layout.MergeSectionLayout,
400 ) model.Error!void {
401     if (use_merge_start_index.* == null) use_merge_start_index.* = elf.merge.shouldIndexStarts(object);
402     const external_starts = if (use_merge_start_index.*.?)
403         try merge_starts.forSection(allocator, object, section_index)
404     else
405         try elf.merge.collectStarts(allocator, object, section_index);
406     defer if (!use_merge_start_index.*.? and external_starts.len != 0) allocator.free(external_starts);
407     try string_merge.register(
408         allocator,
409         object,
410         section,
411         section_index,
412         external_starts,
413         output_sections,
414         &merge_sections[section_index],
415     );
416 }
417 
418 const ChainContext = struct {
419     allocator: Allocator,
420     objects: []const ObjectFile,
421     layouts: []ObjectLayout,
422     classification: Classification,
423     eh_pending: []const elf.ehframe.Scan.Pending,
424     output_sections: *[output_section_count]OutputSection,
425     globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
426     options: model.LinkOptions,
427     failures: *WalkFailures,
428 };
429 
430 fn assignChains(
431     allocator: Allocator,
432     objects: []const ObjectFile,
433     layouts: []ObjectLayout,
434     classification: Classification,
435     eh_pending: []const elf.ehframe.Scan.Pending,
436     output_sections: *[output_section_count]OutputSection,
437     globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
438     options: model.LinkOptions,
439 ) model.Error!?WalkFailure {
440     const requested_workers = if (options.max_link_jobs != 0) options.max_link_jobs else 0;
441     const workers = if (classification.classes.len >= parallel_section_threshold)
442         parallel.chooseWorkers(output_section_count, requested_workers)
443     else
444         1;
445 
446     var failures = try WalkFailures.init(allocator, workers, .{});
447     defer failures.deinit(allocator);
448 
449     var locked_allocator = allocators.LockedAllocator.init(allocator);
450     var context = ChainContext{
451         .allocator = locked_allocator.allocator(),
452         .objects = objects,
453         .layouts = layouts,
454         .classification = classification,
455         .eh_pending = eh_pending,
456         .output_sections = output_sections,
457         .globals = globals,
458         .options = options,
459         .failures = &failures,
460     };
461     if (workers <= 1) {
462         context.allocator = allocator;
463         var chain_index: usize = 0;
464         while (chain_index < output_section_count) : (chain_index += 1) assignChain(&context, 0, chain_index);
465     } else {
466         parallel.forItems(output_section_count, workers, &context, assignChain);
467     }
468     return failures.earliest(WalkFailure.found, WalkFailure.before);
469 }
470 
471 fn assignChain(context: *ChainContext, worker: usize, chain_index: usize) void {
472     const is_eh_chain = chain_index == @backingInt(OutputSectionKind.eh_frame);
473     const is_bss_chain = chain_index == @backingInt(OutputSectionKind.bss);
474     var eh_cursor: usize = 0;
475     var fixed_merge: elf.merge.Fixed = .{};
476     defer fixed_merge.deinit(context.allocator);
477 
478     for (context.objects, 0..) |object, object_index| {
479         const object_base = context.classification.object_bases[object_index];
480         const contributions = context.layouts[object_index].sections;
481         for (object.sections, 0..) |section, section_index| {
482             const flat = object_base + section_index;
483             switch (context.classification.classes[flat]) {
484                 .ehframe => {
485                     if (!is_eh_chain) continue;
486                     const entry = context.eh_pending[eh_cursor];
487                     std.debug.assert(entry.object_index == object_index);
488                     std.debug.assert(entry.section_index == section_index);
489                     eh_cursor += 1;
490                     assignEhFrameSection(context, entry, section, section_index, contributions) catch |err| {
491                         recordWalkFailure(context.failures, worker, object_index, section_index, err);
492                         return;
493                     };
494                 },
495                 .payload => {
496                     if (context.classification.outputs[flat] != chain_index) continue;
497                     payloadSection(context.output_sections, contributions, section, section_index, chain_index, context.options) catch |err| {
498                         recordWalkFailure(context.failures, worker, object_index, section_index, err);
499                         return;
500                     };
501                 },
502                 .empty_alloc => {
503                     if (context.classification.outputs[flat] != chain_index) continue;
504                     emptyAllocSection(context.output_sections, contributions, section, section_index, chain_index) catch |err| {
505                         recordWalkFailure(context.failures, worker, object_index, section_index, err);
506                         return;
507                     };
508                 },
509                 .fixed_merge => {
510                     if (context.classification.outputs[flat] != chain_index) continue;
511                     fixed_merge.collect(
512                         context.allocator,
513                         object,
514                         section,
515                         section_index,
516                         context.output_sections,
517                         context.layouts[object_index].merge_sections,
518                         context.options,
519                     ) catch |err| {
520                         recordWalkFailure(context.failures, worker, object_index, section_index, err);
521                         return;
522                     };
523                 },
524                 .none, .string_merge => {},
525             }
526         }
527         if (is_bss_chain and object.has_common_symbols and context.layouts[object_index].common_symbols.len != 0) {
528             commonSymbols(
529                 object,
530                 object_index,
531                 context.output_sections,
532                 context.layouts[object_index].common_symbols,
533                 context.globals,
534                 context.options,
535             ) catch |err| {
536                 recordWalkFailure(context.failures, worker, object_index, object.sections.len, err);
537                 return;
538             };
539         }
540     }
541 }
542 
543 fn assignEhFrameSection(
544     context: *ChainContext,
545     entry: elf.ehframe.Scan.Pending,
546     section: SectionHeader,
547     section_index: usize,
548     contributions: []SectionContribution,
549 ) model.Error!void {
550     try entry.failure;
551     try elf.ehframe.section.assign(
552         context.output_sections,
553         contributions,
554         section,
555         section_index,
556         entry.output_size,
557         context.options,
558     );
559 }
560 
561 fn initObjectLayouts(
562     objects: []const ObjectFile,
563     layouts: []ObjectLayout,
564     section_contributions: []SectionContribution,
565 ) void {
566     var section_contribution_cursor: usize = 0;
567     for (objects, 0..) |object, object_index| {
568         const next_cursor = section_contribution_cursor + object.sections.len;
569         layouts[object_index] = .{
570             .sections = section_contributions[section_contribution_cursor..next_cursor],
571             .common_symbols = &.{},
572             .merge_sections = &.{},
573             .eh_frame_sections = &.{},
574         };
575         section_contribution_cursor = next_cursor;
576     }
577     std.debug.assert(section_contribution_cursor == section_contributions.len);
578 }
579 
580 pub fn countObjectSections(objects: []const ObjectFile) model.Error!usize {
581     var total: usize = 0;
582     for (objects) |object| {
583         if (object.sections.len > std.math.maxInt(usize) - total) return error.InvalidObject;
584         total += object.sections.len;
585     }
586     return total;
587 }
588 
589 fn emptyAllocSection(
590     output_sections: *[output_section_count]OutputSection,
591     contributions: []SectionContribution,
592     section: SectionHeader,
593     section_index: usize,
594     output_index: usize,
595 ) model.Error!void {
596     var output = &output_sections[output_index];
597     try validateAlignment(section.alignment);
598     const alignment = @max(section.alignment, 1);
599     output.alignment = @max(output.alignment, alignment);
600     const offset = if (section.section_type == std.elf.SHT_NOBITS)
601         alignForwardU64(output.memory_size, alignment)
602     else
603         alignForwardU64(output.file_size, alignment);
604     contributions[section_index] = SectionContribution.init(output_index, offset, 0, 0, alignment);
605 }
606 
607 fn payloadSize(
608     output_sections: *[output_section_count]OutputSection,
609     contributions: []SectionContribution,
610     section: SectionHeader,
611     section_index: usize,
612     output_index: usize,
613     size: u64,
614     options: model.LinkOptions,
615 ) model.Error!void {
616     var output = &output_sections[output_index];
617     try validateAlignment(section.alignment);
618     output.alignment = @max(output.alignment, @max(section.alignment, 1));
619     const alignment = @max(section.alignment, 1);
620     const aligned_offset = if (section.section_type == std.elf.SHT_NOBITS)
621         alignForwardU64(output.memory_size, alignment)
622     else
623         alignForwardU64(output.file_size, alignment);
624     const reserved_size = contributionReserveSize(size, alignment, options);
625     contributions[section_index] = SectionContribution.init(
626         output_index,
627         aligned_offset,
628         size,
629         reserved_size,
630         alignment,
631     );
632 
633     if (section.section_type == std.elf.SHT_NOBITS) {
634         output.memory_size = aligned_offset + reserved_size;
635     } else {
636         output.file_size = aligned_offset + reserved_size;
637         output.memory_size = output.file_size;
638     }
639 }
640 
641 fn payloadSection(
642     output_sections: *[output_section_count]OutputSection,
643     contributions: []SectionContribution,
644     section: SectionHeader,
645     section_index: usize,
646     output_index: usize,
647     options: model.LinkOptions,
648 ) model.Error!void {
649     try payloadSize(output_sections, contributions, section, section_index, output_index, section.size, options);
650 }
651 
652 fn commonSymbols(
653     object: ObjectFile,
654     object_index: usize,
655     output_sections: *[output_section_count]OutputSection,
656     contributions: []SectionContribution,
657     globals: *const std.StringHashMapUnmanaged(GlobalSymbol),
658     options: model.LinkOptions,
659 ) model.Error!void {
660     for (object.symbols, 0..) |symbol, symbol_index| {
661         if (!commonSymbolEligible(object_index, symbol, symbol_index, globals)) continue;
662 
663         const alignment = @max(symbol.value, 1);
664         try validateAlignment(alignment);
665         const output_index = @backingInt(OutputSectionKind.bss);
666         var output = &output_sections[output_index];
667         output.alignment = @max(output.alignment, alignment);
668         const aligned_offset = alignForwardU64(output.memory_size, alignment);
669         const reserved_size = contributionReserveSize(symbol.size, alignment, options);
670         contributions[symbol_index] = SectionContribution.init(
671             output_index,
672             aligned_offset,
673             symbol.size,
674             reserved_size,
675             alignment,
676         );
677         output.memory_size = aligned_offset + reserved_size;
678     }
679 }
680 
681 fn ensureCommonSymbolLayouts(
682     allocator: Allocator,
683     object: ObjectFile,
684     common_symbols: *[]SectionContribution,
685 ) Allocator.Error![]SectionContribution {
686     if (common_symbols.*.len != 0) return common_symbols.*;
687     const symbols = try allocator.alloc(SectionContribution, object.symbols.len);
688     @memset(symbols, .{});
689     common_symbols.* = symbols;
690     return symbols;
691 }