lib/tldr/src/profiling/suite/planning.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const tldr = @import("tldr");
3
4 const incremental = tldr.incremental;
5 const trace = tldr.trace;
6 const Allocator = std.mem.Allocator;
7
8 pub const Workload = struct {
9 allocator: Allocator,
10 state: incremental.PreparedState,
11 replacements: []const incremental.ReplacementContribution,
12 image: []u8,
13 expected_decision: incremental.PatchDecision,
14 input_changes: incremental.InputChanges = .{},
15 candidate_manifest: incremental.Manifest = incremental.Manifest.empty(),
16 candidate_image: []u8 = &.{},
17 inputs: []const tldr.Input = &.{},
18 expected_replacements: usize = 0,
19 accepted_relink_plan: incremental.RelinkPlan = .{ .decision = .in_place },
20 manifest_binary: []const u8 = &.{},
21 expected_manifest_contributions: usize = 0,
22 };
23
24 pub const Row = enum {
25 ordered_64,
26 ordered_256,
27 ordered_1024,
28 disordered_256,
29 archive_churn_256,
30 section_growth_256,
31 relocation_churn_256,
32 symbol_cascade_256,
33 candidate_extract_sparse_256,
34 candidate_extract_dense_256,
35 candidate_update_discarded_256,
36 manifest_decode_256,
37 manifest_decode_1024,
38 };
39
40 const Pattern = enum {
41 ordered,
42 disordered,
43 archive_churn,
44 section_growth,
45 relocation_churn,
46 symbol_cascade,
47 };
48
49 const row_count = @typeInfo(Row).@"enum".field_names.len;
50
51 pub const Set = struct {
52 rows: [row_count]Workload,
53 };
54
55 var active: ?Set = null;
56
57 pub fn install(workloads: Set) void {
58 active = workloads;
59 }
60
61 pub fn clear() void {
62 active = null;
63 }
64
65 pub fn planLinearOrdered64(_: Allocator) void {
66 planLinear(.ordered_64);
67 }
68
69 pub fn planIndexedOrdered64(_: Allocator) void {
70 planIndexed(.ordered_64);
71 }
72
73 pub fn planLinearOrdered256(_: Allocator) void {
74 planLinear(.ordered_256);
75 }
76
77 pub fn planIndexedOrdered256(_: Allocator) void {
78 planIndexed(.ordered_256);
79 }
80
81 pub fn planLinearOrdered1024(_: Allocator) void {
82 planLinear(.ordered_1024);
83 }
84
85 pub fn planIndexedOrdered1024(_: Allocator) void {
86 planIndexed(.ordered_1024);
87 }
88
89 pub fn planLinearDisordered256(_: Allocator) void {
90 planLinear(.disordered_256);
91 }
92
93 pub fn planIndexedDisordered256(_: Allocator) void {
94 planIndexed(.disordered_256);
95 }
96
97 pub fn planLinearArchiveChurn256(_: Allocator) void {
98 planLinear(.archive_churn_256);
99 }
100
101 pub fn planIndexedArchiveChurn256(_: Allocator) void {
102 planIndexed(.archive_churn_256);
103 }
104
105 pub fn planLinearSectionGrowth256(_: Allocator) void {
106 planLinear(.section_growth_256);
107 }
108
109 pub fn planIndexedSectionGrowth256(_: Allocator) void {
110 planIndexed(.section_growth_256);
111 }
112
113 pub fn planLinearRelocationChurn256(_: Allocator) void {
114 planLinear(.relocation_churn_256);
115 }
116
117 pub fn planIndexedRelocationChurn256(_: Allocator) void {
118 planIndexed(.relocation_churn_256);
119 }
120
121 pub fn planLinearSymbolCascade256(_: Allocator) void {
122 planLinear(.symbol_cascade_256);
123 }
124
125 pub fn planIndexedSymbolCascade256(_: Allocator) void {
126 planIndexed(.symbol_cascade_256);
127 }
128
129 pub fn applyLinearArchiveChurn256(_: Allocator) void {
130 applyLinear(.archive_churn_256);
131 }
132
133 pub fn applyIndexedArchiveChurn256(_: Allocator) void {
134 applyIndexed(.archive_churn_256);
135 }
136
137 pub fn applyLinearSectionGrowth256(_: Allocator) void {
138 applyLinear(.section_growth_256);
139 }
140
141 pub fn applyIndexedSectionGrowth256(_: Allocator) void {
142 applyIndexed(.section_growth_256);
143 }
144
145 pub fn applyLinearSymbolCascade256(_: Allocator) void {
146 applyLinear(.symbol_cascade_256);
147 }
148
149 pub fn applyIndexedSymbolCascade256(_: Allocator) void {
150 applyIndexed(.symbol_cascade_256);
151 }
152
153 pub fn extractCandidateSparse256(allocator: Allocator) void {
154 extractCandidate(.candidate_extract_sparse_256, allocator);
155 }
156
157 pub fn extractCandidateDense256(allocator: Allocator) void {
158 extractCandidate(.candidate_extract_dense_256, allocator);
159 }
160
161 pub fn updateCandidateDiscarded256(_: Allocator) void {
162 const phase = trace.product(.manifest_recording);
163 defer phase.end();
164 const workload = activeMutableWorkload(.candidate_update_discarded_256);
165 workload.state.updateManifestForAcceptedCandidateRelinkFromInputChanges(
166 workload.allocator,
167 workload.inputs,
168 workload.input_changes,
169 workload.replacements,
170 workload.candidate_manifest,
171 workload.accepted_relink_plan,
172 ) catch @panic("accepted candidate relink manifest update failed");
173 std.mem.doNotOptimizeAway(workload.state.manifest.discarded_contributions.len);
174 std.mem.doNotOptimizeAway(workload.state.input_contributions.len);
175 }
176
177 pub fn decodeBinaryManifest256(allocator: Allocator) void {
178 decodeManifest(.manifest_decode_256, allocator);
179 }
180
181 pub fn decodeBinaryManifest1024(allocator: Allocator) void {
182 decodeManifest(.manifest_decode_1024, allocator);
183 }
184
185 fn planLinear(row: Row) void {
186 const phase = trace.product(.manifest_recording);
187 defer phase.end();
188 const workload = activeWorkload(row);
189 const plan = workload.state.manifest.planContributionReplacement(workload.replacements);
190 requireExpectedDecision(workload, plan);
191 std.mem.doNotOptimizeAway(plan.decision);
192 std.mem.doNotOptimizeAway(plan.blocker);
193 }
194
195 fn planIndexed(row: Row) void {
196 const phase = trace.product(.manifest_recording);
197 defer phase.end();
198 const workload = activeWorkload(row);
199 const plan = workload.state.planContributionReplacement(workload.replacements);
200 requireExpectedDecision(workload, plan);
201 std.mem.doNotOptimizeAway(plan.decision);
202 std.mem.doNotOptimizeAway(plan.blocker);
203 }
204
205 fn applyLinear(row: Row) void {
206 const phase = trace.product(.output_writing);
207 defer phase.end();
208 const workload = activeWorkload(row);
209 const application = workload.state.manifest.applyContributionReplacement(workload.image, workload.replacements) catch
210 @panic("incremental linear patch application failed");
211 requireExpectedDecision(workload, application.plan);
212 std.mem.doNotOptimizeAway(application.contributions_written);
213 std.mem.doNotOptimizeAway(application.bytes_written);
214 std.mem.doNotOptimizeAway(application.zero_fill_bytes);
215 }
216
217 fn applyIndexed(row: Row) void {
218 const phase = trace.product(.output_writing);
219 defer phase.end();
220 const workload = activeWorkload(row);
221 const application = workload.state.applyContributionReplacement(workload.image, workload.replacements) catch
222 @panic("incremental indexed patch application failed");
223 requireExpectedDecision(workload, application.plan);
224 std.mem.doNotOptimizeAway(application.contributions_written);
225 std.mem.doNotOptimizeAway(application.bytes_written);
226 std.mem.doNotOptimizeAway(application.zero_fill_bytes);
227 }
228
229 fn extractCandidate(row: Row, allocator: Allocator) void {
230 const phase = trace.product(.manifest_recording);
231 defer phase.end();
232 const workload = activeWorkload(row);
233 const replacements = incremental.replacementContributionsFromImage(
234 allocator,
235 workload.candidate_image,
236 workload.candidate_manifest,
237 workload.input_changes,
238 ) catch @panic("candidate replacement extraction failed");
239 defer if (replacements.len != 0) allocator.free(replacements);
240 if (replacements.len != workload.expected_replacements) @panic("candidate replacement extraction count changed");
241 std.mem.doNotOptimizeAway(replacements.len);
242 }
243
244 fn decodeManifest(row: Row, allocator: Allocator) void {
245 const phase = trace.product(.manifest_recording);
246 defer phase.end();
247 const workload = activeWorkload(row);
248 const manifest_bytes = allocator.dupe(u8, workload.manifest_binary) catch @panic("incremental manifest bytes failed");
249 defer allocator.free(manifest_bytes);
250 var manifest = incremental.Manifest.fromBinary(allocator, manifest_bytes) catch @panic("incremental manifest decode failed");
251 defer manifest.deinit(allocator);
252 if (manifest.contributions.len != workload.expected_manifest_contributions) @panic("incremental manifest decode count changed");
253 std.mem.doNotOptimizeAway(manifest.inputs.len);
254 std.mem.doNotOptimizeAway(manifest.sections.len);
255 std.mem.doNotOptimizeAway(manifest.contributions.len);
256 std.mem.doNotOptimizeAway(manifest.discarded_contributions.len);
257 }
258
259 fn activeWorkload(row: Row) *const Workload {
260 if (active) |*workloads| return &workloads.rows[@backingInt(row)];
261 @panic("incremental-planning workloads are not installed");
262 }
263
264 fn activeMutableWorkload(row: Row) *Workload {
265 if (active) |*workloads| return &workloads.rows[@backingInt(row)];
266 @panic("incremental-planning workloads are not installed");
267 }
268
269 fn requireExpectedDecision(workload: *const Workload, plan: incremental.PatchPlan) void {
270 if (plan.decision != workload.expected_decision) @panic("incremental plan returned unexpected decision");
271 }
272
273 pub fn buildSet(allocator: Allocator) !Set {
274 return .{ .rows = .{
275 try build(allocator, 64, .ordered),
276 try build(allocator, 256, .ordered),
277 try build(allocator, 1024, .ordered),
278 try build(allocator, 256, .disordered),
279 try build(allocator, 256, .archive_churn),
280 try build(allocator, 256, .section_growth),
281 try build(allocator, 256, .relocation_churn),
282 try build(allocator, 256, .symbol_cascade),
283 try buildCandidateExtraction(allocator, 256, 8),
284 try buildCandidateExtraction(allocator, 256, 128),
285 try buildCandidateDiscardedUpdate(allocator, 256),
286 try buildManifestDecode(allocator, 256),
287 try buildManifestDecode(allocator, 1024),
288 } };
289 }
290
291 fn build(allocator: Allocator, contribution_count: usize, pattern: Pattern) !Workload {
292 var builder = try incremental.Builder.init(allocator, .{});
293 defer builder.deinit();
294
295 const base = try allocator.alloc(incremental.ReplacementContribution, contribution_count);
296 defer allocator.free(base);
297 const replacements = try allocator.alloc(incremental.ReplacementContribution, contribution_count);
298 errdefer allocator.free(replacements);
299
300 var index: usize = 0;
301 while (index < contribution_count) : (index += 1) {
302 const input_name = try inputName(allocator, pattern, index);
303 const contribution_name = try contributionName(allocator, pattern, index);
304 const contribution_kind = contributionKind(pattern);
305 const output_section_name = outputSectionName(pattern);
306 const size = recordedSize(pattern);
307 const reserve = reservedSize(pattern);
308 const alignment = recordedAlignment(pattern);
309 try builder.addContribution(
310 input_name,
311 index,
312 contribution_kind,
313 contribution_name,
314 @intCast(index),
315 output_section_name,
316 0x401000 + index * 64,
317 0x1000 + index * 64,
318 size,
319 reserve,
320 alignment,
321 );
322 base[index] = .{
323 .input_name = input_name,
324 .input_index = index,
325 .kind = contribution_kind,
326 .name = contribution_name,
327 .ordinal = @intCast(index),
328 .size = replacementSize(pattern),
329 .alignment = replacementAlignment(pattern),
330 .payload = try replacementPayload(allocator, pattern, index, replacementSize(pattern)),
331 };
332 }
333
334 index = 0;
335 while (index < contribution_count) : (index += 1) {
336 replacements[index] = base[replacementSourceIndex(pattern, index, contribution_count)];
337 }
338 if (pattern == .archive_churn) {
339 replacements[contribution_count - 1] = .{
340 .input_name = try std.fmt.allocPrint(allocator, "libhot.a(member_{d}.o)", .{contribution_count}),
341 .input_index = contribution_count,
342 .kind = .section,
343 .name = try std.fmt.allocPrint(allocator, ".text.member.{d}", .{contribution_count}),
344 .ordinal = @intCast(contribution_count),
345 .size = 32,
346 .alignment = 16,
347 .payload = try replacementPayload(allocator, pattern, contribution_count, 32),
348 };
349 }
350 if (pattern == .symbol_cascade) {
351 replacements[contribution_count - 1] = .{
352 .input_name = try std.fmt.allocPrint(allocator, "cascade_{d}.o", .{contribution_count}),
353 .input_index = contribution_count,
354 .kind = .common_symbol,
355 .name = try std.fmt.allocPrint(allocator, "cascade_symbol_{d}", .{contribution_count}),
356 .ordinal = @intCast(contribution_count),
357 .size = 16,
358 .alignment = 8,
359 .payload = try replacementPayload(allocator, pattern, contribution_count, 16),
360 };
361 }
362
363 var manifest = try builder.finish();
364 var manifest_moved = false;
365 errdefer if (!manifest_moved) manifest.deinit(allocator);
366
367 manifest_moved = true;
368 var state = try incremental.PreparedState.fromOwnedManifest(allocator, manifest);
369 errdefer state.deinit(allocator);
370 try state.ensureReplacementIndex(allocator, replacements);
371
372 return .{
373 .allocator = allocator,
374 .state = state,
375 .replacements = replacements,
376 .image = try outputImage(allocator, contribution_count),
377 .expected_decision = expectedDecision(pattern),
378 };
379 }
380
381 fn buildManifestDecode(allocator: Allocator, contribution_count: usize) !Workload {
382 var builder = try incremental.Builder.init(allocator, .{ .incremental_mode = .prepare, .gc_sections = true, .icf = .all, .strip_debug = false });
383 defer builder.deinit();
384
385 try builder.addSection(".text", 0x401000, 0x1000, contribution_count * 48, contribution_count * 64, 16);
386 try builder.addSection(".rodata", 0x501000, 0x9000, contribution_count * 32, contribution_count * 32, 8);
387 try builder.addSection(".tdata", 0x601000, 0xd000, contribution_count * 16, contribution_count * 16, 8);
388 try builder.addSection(".tbss", 0x701000, 0, contribution_count * 16, contribution_count * 16, 8);
389
390 var index: usize = 0;
391 while (index < contribution_count) : (index += 1) {
392 const input_name = try std.fmt.allocPrint(allocator, "decode_{d}.o", .{index});
393 try builder.addInput(.{ .name = input_name, .bytes = "decode-input" });
394 const output_section_name = manifestDecodeSectionName(index);
395 const contribution_name = try std.fmt.allocPrint(allocator, "{s}.decode.{d}", .{ output_section_name, index });
396 const file_size: u64 = if (std.mem.eql(u8, output_section_name, ".tbss")) 0 else manifestDecodeSize(index);
397 try builder.addContributionWithFileSize(
398 input_name,
399 index,
400 .section,
401 contribution_name,
402 @intCast(index),
403 output_section_name,
404 0x401000 + index * 64,
405 0x1000 + index * 64,
406 manifestDecodeSize(index),
407 file_size,
408 64,
409 manifestDecodeAlignment(index),
410 );
411 if (index % 8 == 0) {
412 const discarded_name = try std.fmt.allocPrint(allocator, ".text.dead.decode.{d}", .{index});
413 try builder.addDiscardedContribution(input_name, index, discarded_name, @intCast(index), .identical_code_folded, 24, 16);
414 }
415 }
416
417 var manifest = try builder.finish();
418 var manifest_moved = false;
419 errdefer if (!manifest_moved) manifest.deinit(allocator);
420
421 const manifest_binary = try manifest.formatBinaryAlloc(allocator);
422 errdefer allocator.free(manifest_binary);
423
424 manifest_moved = true;
425 var state = try incremental.PreparedState.fromOwnedManifest(allocator, manifest);
426 errdefer state.deinit(allocator);
427
428 return .{
429 .allocator = allocator,
430 .state = state,
431 .replacements = &.{},
432 .image = &.{},
433 .expected_decision = .in_place,
434 .manifest_binary = manifest_binary,
435 .expected_manifest_contributions = contribution_count,
436 };
437 }
438
439 fn manifestDecodeSectionName(index: usize) []const u8 {
440 return switch (index % 4) {
441 0 => ".text",
442 1 => ".rodata",
443 2 => ".tdata",
444 else => ".tbss",
445 };
446 }
447
448 fn manifestDecodeSize(index: usize) u64 {
449 return switch (index % 4) {
450 0 => 48,
451 1 => 32,
452 else => 16,
453 };
454 }
455
456 fn manifestDecodeAlignment(index: usize) u64 {
457 return switch (index % 4) {
458 0 => 16,
459 1 => 8,
460 else => 4,
461 };
462 }
463
464 fn buildCandidateExtraction(allocator: Allocator, contribution_count: usize, changed_count: usize) !Workload {
465 var builder = try incremental.Builder.init(allocator, .{});
466 defer builder.deinit();
467 var candidate_builder = try incremental.Builder.init(allocator, .{});
468 defer candidate_builder.deinit();
469
470 var changes = try allocator.alloc(incremental.InputChange, contribution_count);
471 var summary: incremental.InputChangeSummary = .{};
472 var index: usize = 0;
473 while (index < contribution_count) : (index += 1) {
474 const name = try std.fmt.allocPrint(allocator, "candidate_{d}.o", .{index});
475 try builder.addInput(.{ .name = name, .bytes = "old" });
476 try candidate_builder.addInput(.{ .name = name, .bytes = if (index < changed_count) "new" else "old" });
477 const contribution_name = try std.fmt.allocPrint(allocator, ".text.candidate.{d}", .{index});
478 try builder.addContribution(
479 name,
480 index,
481 .section,
482 contribution_name,
483 @intCast(index),
484 ".text",
485 0x401000 + index * 64,
486 0x1000 + index * 64,
487 32,
488 64,
489 16,
490 );
491 try candidate_builder.addContribution(
492 name,
493 index,
494 .section,
495 contribution_name,
496 @intCast(index),
497 ".text",
498 0x401000 + index * 64,
499 0x1000 + index * 64,
500 48,
501 64,
502 16,
503 );
504 changes[index] = .{
505 .kind = if (index < changed_count) .changed else .unchanged,
506 .recorded_index = index,
507 .current_index = index,
508 };
509 if (index < changed_count) {
510 summary.changed += 1;
511 } else {
512 summary.unchanged += 1;
513 }
514 }
515
516 var manifest = try builder.finish();
517 var manifest_moved = false;
518 errdefer if (!manifest_moved) manifest.deinit(allocator);
519 manifest_moved = true;
520 var state = try incremental.PreparedState.fromOwnedManifest(allocator, manifest);
521 errdefer state.deinit(allocator);
522
523 return .{
524 .allocator = allocator,
525 .state = state,
526 .replacements = &.{},
527 .image = try outputImage(allocator, contribution_count),
528 .expected_decision = .in_place,
529 .input_changes = .{
530 .changes = changes,
531 .summary = summary,
532 },
533 .candidate_manifest = try candidate_builder.finish(),
534 .candidate_image = try outputImage(allocator, contribution_count),
535 .expected_replacements = changed_count,
536 };
537 }
538
539 fn buildCandidateDiscardedUpdate(allocator: Allocator, input_count: usize) !Workload {
540 var builder = try incremental.Builder.init(allocator, .{});
541 defer builder.deinit();
542 var candidate_builder = try incremental.Builder.init(allocator, .{});
543 defer candidate_builder.deinit();
544
545 const inputs = try allocator.alloc(tldr.Input, input_count);
546 var changes = try allocator.alloc(incremental.InputChange, input_count);
547 var index: usize = 0;
548 while (index < input_count) : (index += 1) {
549 const name = try std.fmt.allocPrint(allocator, "discarded_{d}.o", .{index});
550 try builder.addInput(.{ .name = name, .bytes = "old" });
551 inputs[index] = .{ .name = name, .bytes = "new" };
552 try candidate_builder.addInput(inputs[index]);
553 const old_name = try std.fmt.allocPrint(allocator, ".text.dead.{d}", .{index});
554 const new_name = try std.fmt.allocPrint(allocator, ".text.folded.{d}", .{index});
555 try builder.addDiscardedContribution(name, index, old_name, @intCast(index), .discarded, 24, 16);
556 try candidate_builder.addDiscardedContribution(name, index, new_name, @intCast(index), .identical_code_folded, 32, 16);
557 changes[index] = .{
558 .kind = .changed,
559 .recorded_index = index,
560 .current_index = index,
561 };
562 }
563
564 var manifest = try builder.finish();
565 var manifest_moved = false;
566 errdefer if (!manifest_moved) manifest.deinit(allocator);
567 manifest_moved = true;
568 var state = try incremental.PreparedState.fromOwnedManifest(allocator, manifest);
569 errdefer state.deinit(allocator);
570
571 const input_changes = incremental.InputChanges{
572 .changes = changes,
573 .summary = .{ .changed = input_count },
574 };
575 const accepted_relink_plan = state.planChangedInputRelinkFromInputChanges(.{}, input_changes, &.{});
576 if (accepted_relink_plan.decision != .in_place) return error.UnexpectedRelinkPlan;
577
578 return .{
579 .allocator = allocator,
580 .state = state,
581 .replacements = &.{},
582 .image = try outputImage(allocator, input_count),
583 .expected_decision = .in_place,
584 .input_changes = input_changes,
585 .candidate_manifest = try candidate_builder.finish(),
586 .candidate_image = &.{},
587 .inputs = inputs,
588 .accepted_relink_plan = accepted_relink_plan,
589 };
590 }
591
592 fn contributionKind(pattern: Pattern) incremental.ContributionKind {
593 return switch (pattern) {
594 .symbol_cascade => .common_symbol,
595 else => .section,
596 };
597 }
598
599 fn inputName(allocator: Allocator, pattern: Pattern, index: usize) ![]u8 {
600 return switch (pattern) {
601 .archive_churn => try std.fmt.allocPrint(allocator, "libhot.a(member_{d}.o)", .{index}),
602 .relocation_churn => try std.fmt.allocPrint(allocator, "reloc_{d}.o", .{index}),
603 .symbol_cascade => try std.fmt.allocPrint(allocator, "cascade_{d}.o", .{index}),
604 else => try std.fmt.allocPrint(allocator, "obj_{d}.o", .{index}),
605 };
606 }
607
608 fn contributionName(allocator: Allocator, pattern: Pattern, index: usize) ![]u8 {
609 return switch (pattern) {
610 .archive_churn => try std.fmt.allocPrint(allocator, ".text.member.{d}", .{index}),
611 .relocation_churn => try std.fmt.allocPrint(allocator, ".rela.text.{d}", .{index}),
612 .symbol_cascade => try std.fmt.allocPrint(allocator, "cascade_symbol_{d}", .{index}),
613 else => try std.fmt.allocPrint(allocator, ".text.{d}", .{index}),
614 };
615 }
616
617 fn outputSectionName(pattern: Pattern) []const u8 {
618 return switch (pattern) {
619 .relocation_churn => ".rela.text",
620 .symbol_cascade => ".bss",
621 else => ".text",
622 };
623 }
624
625 fn recordedSize(pattern: Pattern) u64 {
626 return switch (pattern) {
627 .relocation_churn => 24,
628 .symbol_cascade => 8,
629 else => 32,
630 };
631 }
632
633 fn replacementSize(pattern: Pattern) u64 {
634 return switch (pattern) {
635 .section_growth => 56,
636 .relocation_churn => 24,
637 .symbol_cascade => 16,
638 else => 48,
639 };
640 }
641
642 fn reservedSize(pattern: Pattern) u64 {
643 return switch (pattern) {
644 .relocation_churn => 32,
645 .symbol_cascade => 16,
646 else => 64,
647 };
648 }
649
650 fn recordedAlignment(pattern: Pattern) u64 {
651 return switch (pattern) {
652 .symbol_cascade => 8,
653 else => 16,
654 };
655 }
656
657 fn replacementAlignment(pattern: Pattern) u64 {
658 return switch (pattern) {
659 .symbol_cascade => 8,
660 else => 16,
661 };
662 }
663
664 fn replacementPayload(allocator: Allocator, pattern: Pattern, index: usize, size: u64) ![]u8 {
665 const bytes = try allocator.alloc(u8, @intCast(size));
666 for (bytes, 0..) |*byte, offset| {
667 byte.* = @truncate((index * 31) + (offset * 17) + @backingInt(pattern));
668 }
669 return bytes;
670 }
671
672 fn outputImage(allocator: Allocator, contribution_count: usize) ![]u8 {
673 const bytes = try allocator.alloc(u8, 0x1000 + contribution_count * 64 + 64);
674 @memset(bytes, 0xa5);
675 return bytes;
676 }
677
678 fn replacementSourceIndex(pattern: Pattern, index: usize, contribution_count: usize) usize {
679 return switch (pattern) {
680 .disordered => contribution_count - 1 - index,
681 .archive_churn, .symbol_cascade => if (index + 1 == contribution_count) index else contribution_count - 2 - index,
682 else => index,
683 };
684 }
685
686 fn expectedDecision(pattern: Pattern) incremental.PatchDecision {
687 return switch (pattern) {
688 .archive_churn, .symbol_cascade => .full_link,
689 else => .in_place,
690 };
691 }
692
693 test "bench: incremental planning workloads cover scale and churn decisions" {
694 var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
695 defer arena_state.deinit();
696 const allocator = arena_state.allocator();
697
698 const workloads = try buildSet(allocator);
699 inline for (
700 @typeInfo(Row).@"enum".field_names,
701 @typeInfo(Row).@"enum".field_values,
702 ) |field_name, field_name_value| {
703 const field = .{ .name = field_name, .value = field_name_value };
704 const row: Row = @fromBackingInt(@intCast(field.value));
705 const workload = workloads.rows[@backingInt(row)];
706 const linear = workload.state.manifest.planContributionReplacement(workload.replacements);
707 const indexed = workload.state.planContributionReplacement(workload.replacements);
708 try std.testing.expectEqual(workload.expected_decision, linear.decision);
709 try std.testing.expectEqual(linear.decision, indexed.decision);
710 try std.testing.expectEqual(linear.blocker, indexed.blocker);
711 try std.testing.expectEqual(linear.blocking_index, indexed.blocking_index);
712 }
713 }
714
715 test "bench: incremental patch workloads apply selected mutation rows" {
716 var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
717 defer arena_state.deinit();
718 const allocator = arena_state.allocator();
719
720 const workloads = try buildSet(allocator);
721 const rows = [_]Row{
722 .archive_churn_256,
723 .section_growth_256,
724 .symbol_cascade_256,
725 };
726 for (rows) |row| {
727 const workload = workloads.rows[@backingInt(row)];
728 const linear_image = try allocator.dupe(u8, workload.image);
729 const indexed_image = try allocator.dupe(u8, workload.image);
730 const linear = try workload.state.manifest.applyContributionReplacement(linear_image, workload.replacements);
731 const indexed = try workload.state.applyContributionReplacement(indexed_image, workload.replacements);
732 try std.testing.expectEqual(workload.expected_decision, linear.plan.decision);
733 try std.testing.expectEqual(linear.plan.decision, indexed.plan.decision);
734 try std.testing.expectEqual(linear.plan.blocker, indexed.plan.blocker);
735 try std.testing.expectEqual(linear.contributions_written, indexed.contributions_written);
736 try std.testing.expectEqual(linear.bytes_written, indexed.bytes_written);
737 if (workload.expected_decision == .in_place) {
738 try std.testing.expect(linear.bytes_written != 0);
739 try std.testing.expectEqualSlices(u8, linear_image, indexed_image);
740 } else {
741 try std.testing.expectEqual(@as(usize, 0), linear.bytes_written);
742 }
743 }
744 }
745
746 test "bench: incremental candidate workloads cover extraction and update" {
747 var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
748 defer arena_state.deinit();
749 const allocator = arena_state.allocator();
750
751 var workloads = try buildSet(allocator);
752 const sparse = workloads.rows[@backingInt(Row.candidate_extract_sparse_256)];
753 const sparse_replacements = try incremental.replacementContributionsFromImage(
754 allocator,
755 sparse.candidate_image,
756 sparse.candidate_manifest,
757 sparse.input_changes,
758 );
759 try std.testing.expectEqual(sparse.expected_replacements, sparse_replacements.len);
760
761 const dense = workloads.rows[@backingInt(Row.candidate_extract_dense_256)];
762 const dense_replacements = try incremental.replacementContributionsFromImage(
763 allocator,
764 dense.candidate_image,
765 dense.candidate_manifest,
766 dense.input_changes,
767 );
768 try std.testing.expectEqual(dense.expected_replacements, dense_replacements.len);
769
770 var update = &workloads.rows[@backingInt(Row.candidate_update_discarded_256)];
771 try update.state.updateManifestForAcceptedCandidateRelinkFromInputChanges(
772 allocator,
773 update.inputs,
774 update.input_changes,
775 update.replacements,
776 update.candidate_manifest,
777 update.accepted_relink_plan,
778 );
779 try std.testing.expectEqual(@as(usize, 256), update.state.manifest.discarded_contributions.len);
780 try std.testing.expectEqual(incremental.DiscardReason.identical_code_folded, update.state.manifest.discarded_contributions[0].reason);
781 try std.testing.expectEqual(@as(u64, 32), update.state.input_contributions[0].discarded_size);
782 }
783
784 test "bench: incremental manifest decode workloads parse private format" {
785 var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
786 defer arena_state.deinit();
787 const allocator = arena_state.allocator();
788
789 const workloads = try buildSet(allocator);
790 const rows = [_]Row{
791 .manifest_decode_256,
792 .manifest_decode_1024,
793 };
794 for (rows) |row| {
795 const workload = workloads.rows[@backingInt(row)];
796 const binary_bytes = try allocator.dupe(u8, workload.manifest_binary);
797 defer allocator.free(binary_bytes);
798 var binary = try incremental.Manifest.fromBinary(allocator, binary_bytes);
799 defer binary.deinit(allocator);
800 try std.testing.expectEqual(workload.expected_manifest_contributions, binary.contributions.len);
801 try std.testing.expectEqual(@as(usize, 4), binary.sections.len);
802 try std.testing.expect(binary.input_hashes_recorded);
803 }
804 }