lib/accy/src/preparation/fingerprint.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 //! Functions that reduce three plans of one compile to a 64-bit number each: how operations are
2 //! grouped into launches and scheduled, how buffers are placed in memory, and how the functions a
3 //! device runs across many threads at once are outlined and generated.
4 //!
5 //! A person comparing two compiles wants to see at a glance whether a middle stage planned the same
6 //! thing, without reading the plans. A short number that looks like an identity invites code to use
7 //! it as one, and a summary that depends on function names or on where values sit in memory changes
8 //! between two compiles of the same plan.
9 //!
10 //! Each summary is written into the per-stage display record of one compile, built for people
11 //! reading it (*run stamp*). No product key, reuse check, receipt identity or other decision reads
12 //! a summary, because the sealed record of each stage (its *stage record*) holds the exact plan.
13 //! Values are numbered by a stable order within the module, and operations are summarized by the
14 //! shape of the tree beneath them, so equal plans inside functions with different names give equal
15 //! summaries. A planned buffer (a *buffer slot*) records only whether it has an enclosing function,
16 //! because the value it holds already identifies that function. Every field of every plan is either
17 //! summarized or listed as administrative, and a compile-time check stops the build when a new
18 //! field is neither.
19
20 const std = @import("std");
21 const choir = @import("choir");
22
23 const accy_choir = @import("../choir/root.zig");
24 const bufferization = @import("bufferization/root.zig");
25 const fusion = @import("fusion/root.zig");
26 const kernelization = @import("kernelization/root.zig");
27 const layout = @import("layout.zig");
28 const memory_space = @import("memory.zig");
29 const outlining = @import("outlining/root.zig");
30 const schedule_planning = @import("schedule/root.zig");
31
32 const ir = choir.ir;
33 const outline_model = kernelization.product;
34 const Builder = choir.product.incremental.FingerprintBuilder;
35 const Numbering = choir.StableValueNumbering;
36
37 /// Returns one 64-bit summary of the fusion plan and the schedule plan of `module`, taken after the
38 /// dispatch pipeline, for the run record. The summary also covers the product name and
39 /// `pipeline_name`. The allocator backs only scratch for value numbering, which is freed before the
40 /// call returns.
41 pub fn dispatch(
42 allocator: std.mem.Allocator,
43 module: *accy_choir.tensor.TensorJob,
44 pipeline_name: []const u8,
45 ) !u64 {
46 comptime dispatchCoverage();
47 var pass_ctx = module.passContext();
48 defer pass_ctx.deinit();
49 const root = module.choir_module;
50 const fusion_plan = try fusion.getFusionPlanAnalysis(&pass_ctx, root);
51 const schedule_plan = try schedule_planning.getSchedulePlanAnalysis(&pass_ctx, root);
52
53 const limits = try Numbering.Limits.inspect(root);
54 var numbering = try Numbering.init(allocator, limits);
55 defer numbering.deinit(allocator);
56 try numbering.activate();
57
58 var builder = Builder{};
59 builder.updateBytes(accy_choir.dispatch.product_name);
60 builder.updateBytes(pipeline_name);
61 updateFusionPlan(&builder, fusion_plan, &numbering);
62 updateSchedulePlan(&builder, schedule_plan, &numbering);
63 return builder.finish();
64 }
65
66 /// Returns one 64-bit summary of the buffer plan, the memory-space plan and the layout plan of
67 /// `module`, taken after the memory pipeline, for the run record. The summary also covers the
68 /// product name and `pipeline_name`. The allocator backs only scratch for value numbering and the
69 /// buffer plan walk, which is freed before the call returns.
70 pub fn memory(
71 allocator: std.mem.Allocator,
72 module: *accy_choir.dispatch.DispatchJob,
73 pipeline_name: []const u8,
74 ) !u64 {
75 comptime memoryCoverage();
76 var pass_ctx = module.passContext();
77 defer pass_ctx.deinit();
78 const root = module.choir_module;
79 const buffer_plan = try bufferization.getBufferPlanAnalysis(&pass_ctx, root);
80 const space_plan = try memory_space.getMemorySpacePlanAnalysis(&pass_ctx, root);
81 const layout_plan = try layout.getLayoutPlanAnalysis(&pass_ctx, root);
82
83 const limits = try Numbering.Limits.inspect(root);
84 var numbering = try Numbering.init(allocator, limits);
85 defer numbering.deinit(allocator);
86 try numbering.activate();
87
88 var builder = Builder{};
89 builder.updateBytes(accy_choir.memory.product_name);
90 builder.updateBytes(pipeline_name);
91 try updateBufferPlan(allocator, &builder, buffer_plan, &numbering);
92 updateSpacePlan(&builder, space_plan, &numbering);
93 updateLayoutPlan(&builder, layout_plan, &numbering);
94 return builder.finish();
95 }
96
97 /// Returns one 64-bit summary of the kernel outline plan and the generated kernels of `module`,
98 /// taken after the kernel pipeline, for the run record. The summary also covers the product name
99 /// and `pipeline_name`. The names of the generated kernels leave the summary unchanged.
100 pub fn kernel(
101 allocator: std.mem.Allocator,
102 module: *accy_choir.memory.MemoryJob,
103 pipeline_name: []const u8,
104 ) !u64 {
105 comptime kernelCoverage();
106 var pass_ctx = module.passContext();
107 defer pass_ctx.deinit();
108 const root = module.choir_module;
109 const outline_plan = try outlining.getKernelOutlinePlanAnalysis(&pass_ctx, root);
110 const kernel_plan = try kernelization.getKernelizationAnalysis(&pass_ctx, root);
111
112 const limits = try Numbering.Limits.inspect(root);
113 var numbering = try Numbering.init(allocator, limits);
114 defer numbering.deinit(allocator);
115 try numbering.activate();
116
117 var builder = Builder{};
118 builder.updateBytes(accy_choir.gpu.product_name);
119 builder.updateBytes(pipeline_name);
120 updateOutlinePlan(&builder, outline_plan, &numbering);
121 try updateKernelPlan(allocator, &builder, kernel_plan);
122 return builder.finish();
123 }
124
125 fn updateFusionPlan(
126 builder: *Builder,
127 plan: *const fusion.FusionPlanAnalysis,
128 numbering: *Numbering,
129 ) void {
130 builder.updateBytes(fusion.fusion_plan_analysis_name);
131 builder.updateUsize(plan.clusters.items.len);
132 builder.updateUsize(plan.fused_op_count);
133 builder.updateUsize(plan.max_cluster_len);
134 for (plan.clusters.items) |cluster| {
135 builder.updateEnumTag(cluster.kind);
136 updateOperations(builder, numbering, cluster.ops);
137 }
138 updateOperations(builder, numbering, plan.elided.items);
139 }
140
141 fn updateSchedulePlan(
142 builder: *Builder,
143 plan: *const schedule_planning.SchedulePlanAnalysis,
144 numbering: *Numbering,
145 ) void {
146 builder.updateBytes(schedule_planning.schedule_plan_analysis_name);
147 builder.updateUsize(plan.work_items.items.len);
148 builder.updateUsize(plan.single_work_count);
149 builder.updateUsize(plan.fusion_work_count);
150 builder.updateUsize(plan.kernel_call_work_count);
151 builder.updateUsize(plan.scheduled_op_count);
152 builder.updateU64(plan.total_static_elements);
153 for (plan.work_items.items) |work| {
154 builder.updateUsize(work.id);
155 builder.updateEnumTag(work.kind);
156 numbering.updateSubtreeFingerprint(builder, work.root);
157 updateOperations(builder, numbering, work.ops);
158 updateValue(builder, numbering, work.output_value);
159 builder.updateU64(@backingInt(work.dtype));
160 builder.updateUsize(work.rank);
161 builder.updateU64(work.element_count);
162 updateResources(builder, work.resources);
163 }
164 }
165
166 fn updateResources(
167 builder: *Builder,
168 resources: schedule_planning.ScheduleResourceEstimate,
169 ) void {
170 builder.updateU64(resources.element_count);
171 builder.updateU64(resources.element_size);
172 builder.updateUsize(resources.op_count);
173 builder.updateUsize(resources.external_input_value_count);
174 builder.updateUsize(resources.external_operand_count);
175 builder.updateUsize(resources.chain_operand_count);
176 builder.updateU64(resources.static_read_bytes);
177 builder.updateU64(resources.static_write_bytes);
178 builder.updateU64(resources.static_total_bytes);
179 builder.updateU64(resources.estimated_element_ops);
180 builder.updateBool(resources.static_bytes_complete);
181 }
182
183 fn updateBufferPlan(
184 allocator: std.mem.Allocator,
185 builder: *Builder,
186 plan: *const bufferization.BufferPlanAnalysis,
187 numbering: *Numbering,
188 ) !void {
189 builder.updateBytes(bufferization.buffer_plan_analysis_name);
190 builder.updateUsize(plan.slots.items.len);
191 builder.updateUsize(plan.elisions.items.len);
192 builder.updateUsize(plan.input_slot_count);
193 builder.updateUsize(plan.output_slot_count);
194 builder.updateUsize(plan.temporary_slot_count);
195 builder.updateUsize(plan.constant_slot_count);
196 builder.updateUsize(plan.dynamic_slot_count);
197 builder.updateU64(plan.total_static_bytes);
198 for (plan.slots.items) |slot| {
199 builder.updateUsize(slot.id);
200 updateValue(builder, numbering, slot.value);
201 updateOptionalOperation(builder, numbering, slot.producer);
202 builder.updateBool(slot.function != null);
203 updateRole(builder, slot.role);
204 builder.updateU64(@backingInt(slot.dtype));
205 builder.updateI64Slice(slot.dims);
206 builder.updateOptionalU64(slot.element_count);
207 builder.updateOptionalU64Slice(slot.row_major_strides);
208 builder.updateOptionalU64(slot.byte_size);
209 }
210 for (plan.elisions.items) |elision| {
211 updateValue(builder, numbering, elision.value);
212 numbering.updateSubtreeFingerprint(builder, elision.producer);
213 numbering.updateSubtreeFingerprint(builder, elision.root);
214 builder.updateUsize(elision.cluster_index);
215 }
216 try updateBindings(allocator, builder, plan, numbering);
217 }
218
219 const Binding = struct {
220 value: u64,
221 slot_id: usize,
222
223 fn lessThan(_: void, lhs: Binding, rhs: Binding) bool {
224 if (lhs.value != rhs.value) return lhs.value < rhs.value;
225 return lhs.slot_id < rhs.slot_id;
226 }
227 };
228
229 fn updateBindings(
230 allocator: std.mem.Allocator,
231 builder: *Builder,
232 plan: *const bufferization.BufferPlanAnalysis,
233 numbering: *const Numbering,
234 ) !void {
235 const bindings = try allocator.alloc(Binding, plan.value_to_slot.count());
236 defer allocator.free(bindings);
237 var entries = plan.value_to_slot.iterator();
238 var count: usize = 0;
239 while (entries.next()) |entry| : (count += 1) {
240 bindings[count] = .{
241 .value = numbering.valueId(entry.key_ptr.*) orelse std.math.maxInt(u64),
242 .slot_id = entry.value_ptr.*,
243 };
244 }
245 std.debug.assert(count == bindings.len);
246 std.mem.sort(Binding, bindings, {}, Binding.lessThan);
247 builder.updateUsize(bindings.len);
248 for (bindings) |binding| {
249 builder.updateU64(binding.value);
250 builder.updateUsize(binding.slot_id);
251 }
252 }
253
254 fn updateSpacePlan(
255 builder: *Builder,
256 plan: *const memory_space.MemorySpacePlanAnalysis,
257 numbering: *Numbering,
258 ) void {
259 builder.updateBytes(memory_space.memory_space_plan_analysis_name);
260 builder.updateUsize(plan.assignments.items.len);
261 builder.updateUsize(plan.host_slot_count);
262 builder.updateUsize(plan.device_global_slot_count);
263 builder.updateUsize(plan.device_constant_slot_count);
264 builder.updateUsize(plan.device_shared_slot_count);
265 builder.updateUsize(plan.unified_slot_count);
266 builder.updateUsize(plan.host_input_transfer_count);
267 builder.updateUsize(plan.host_output_transfer_count);
268 builder.updateUsize(plan.dynamic_slot_count);
269 builder.updateUsize(plan.elided_value_count);
270 builder.updateU64(plan.total_static_bytes);
271 for (plan.assignments.items) |assignment| {
272 builder.updateUsize(assignment.slot_id);
273 updateValue(builder, numbering, assignment.value);
274 updateOptionalOperation(builder, numbering, assignment.producer);
275 updateRole(builder, assignment.role);
276 builder.updateEnumTag(assignment.space);
277 builder.updateEnumTag(assignment.access);
278 builder.updateEnumTag(assignment.transfer);
279 builder.updateOptionalU64(assignment.byte_size);
280 updateOutputSource(builder, assignment.output_source);
281 }
282 }
283
284 fn updateOutputSource(builder: *Builder, source: ?memory_space.OutputSource) void {
285 if (source) |value| {
286 builder.updateBool(true);
287 builder.updateEnumTag(std.meta.activeTag(value));
288 switch (value) {
289 inline else => |index| builder.updateUsize(index),
290 }
291 } else {
292 builder.updateBool(false);
293 }
294 }
295
296 fn updateLayoutPlan(
297 builder: *Builder,
298 plan: *const layout.LayoutPlanAnalysis,
299 numbering: *Numbering,
300 ) void {
301 builder.updateBytes(layout.layout_plan_analysis_name);
302 builder.updateUsize(plan.assignments.items.len);
303 builder.updateUsize(plan.scalar_layout_count);
304 builder.updateUsize(plan.row_major_layout_count);
305 builder.updateUsize(plan.dynamic_row_major_layout_count);
306 builder.updateUsize(plan.host_slot_count);
307 builder.updateUsize(plan.device_global_slot_count);
308 builder.updateUsize(plan.device_constant_slot_count);
309 builder.updateUsize(plan.device_shared_slot_count);
310 builder.updateUsize(plan.unified_slot_count);
311 builder.updateUsize(plan.dynamic_slot_count);
312 builder.updateUsize(plan.elided_value_count);
313 builder.updateU64(plan.total_static_bytes);
314 for (plan.assignments.items) |assignment| {
315 updateLayout(builder, assignment, numbering);
316 }
317 }
318
319 fn updateLayout(
320 builder: *Builder,
321 assignment: layout.LayoutAssignment,
322 numbering: *Numbering,
323 ) void {
324 builder.updateUsize(assignment.slot_id);
325 updateValue(builder, numbering, assignment.value);
326 updateOptionalOperation(builder, numbering, assignment.producer);
327 updateRole(builder, assignment.role);
328 builder.updateU64(@backingInt(assignment.dtype));
329 builder.updateEnumTag(assignment.memory_space);
330 builder.updateEnumTag(assignment.kind);
331 builder.updateUsize(assignment.rank);
332 builder.updateI64Slice(assignment.dims);
333 builder.updateOptionalU64Slice(assignment.element_strides);
334 builder.updateUsizeSlice(assignment.minor_to_major);
335 builder.updateOptionalU64(assignment.element_count);
336 builder.updateOptionalU64(assignment.byte_size);
337 builder.updateU64(assignment.element_size);
338 builder.updateU64(assignment.alignment);
339 builder.updateBool(assignment.contiguous);
340 builder.updateBool(assignment.static_layout);
341 }
342
343 fn updateOutlinePlan(
344 builder: *Builder,
345 plan: *const outline_model.KernelOutlinePlanAnalysis,
346 numbering: *Numbering,
347 ) void {
348 builder.updateBytes(outline_model.kernel_outline_plan_analysis_name);
349 builder.updateUsize(plan.kernels.items.len);
350 builder.updateUsize(plan.total_input_slots);
351 builder.updateUsize(plan.total_scheduled_ops);
352 for (plan.kernels.items) |outline| {
353 builder.updateUsize(outline.id);
354 builder.updateBytes(outline.name);
355 builder.updateEnumTag(outline.kind);
356 builder.updateUsize(outline.work_item_id);
357 numbering.updateSubtreeFingerprint(builder, outline.root);
358 builder.updateUsizeSlice(outline.input_slot_ids);
359 builder.updateUsize(outline.output_slot_id);
360 builder.updateU64(outline.element_count);
361 builder.updateUsize(outline.op_count);
362 }
363 }
364
365 fn updateKernelPlan(
366 allocator: std.mem.Allocator,
367 builder: *Builder,
368 plan: *const kernelization.KernelizationAnalysis,
369 ) !void {
370 builder.updateBytes(kernelization.kernelization_analysis_name);
371 builder.updateUsize(plan.kernels.items.len);
372 for (plan.kernels.items) |*lowered| {
373 builder.updateUsize(lowered.work_item_id);
374 builder.updateBytes(lowered.entry_name);
375 builder.updateU64(try lowered.program.fingerprint(allocator));
376 builder.updateU32(lowered.argument_count);
377 builder.updateU64(lowered.body_fingerprint);
378 builder.updateU32(lowered.dynamic_shared_memory_bytes);
379 builder.updateEnumTag(lowered.schedule.kind);
380 builder.updateU32(lowered.schedule.threads.x);
381 builder.updateU32(lowered.schedule.threads.y);
382 builder.updateU32(lowered.schedule.threads.z);
383 if (lowered.launch) |launch| {
384 builder.updateBool(true);
385 for (launch.grid) |extent| builder.updateU32(extent);
386 for (launch.block) |extent| builder.updateU32(extent);
387 } else {
388 builder.updateBool(false);
389 }
390 builder.updateOptionalU32(lowered.output_fill_pattern);
391 builder.updateOptionalU32(lowered.scratch_fill_pattern);
392 updateBody(builder, lowered.body);
393 }
394 }
395
396 fn updateBody(builder: *Builder, body: kernelization.LoweredKernelBody) void {
397 builder.updateEnumTag(std.meta.activeTag(body));
398 switch (body) {
399 .generic => {},
400 inline else => |payload| updateScalars(builder, payload),
401 }
402 }
403
404 fn updateScalars(builder: *Builder, value: anytype) void {
405 const Value = @TypeOf(value);
406 switch (@typeInfo(Value)) {
407 .int => builder.updateU64(value),
408 .@"struct" => |info| inline for (info.field_names) |name| {
409 builder.updateU64(@field(value, name));
410 },
411 else => @compileError("unsummarized kernel body payload: " ++ @typeName(Value)),
412 }
413 }
414
415 fn updateOperations(
416 builder: *Builder,
417 numbering: *Numbering,
418 operations: []const *ir.Operation,
419 ) void {
420 builder.updateUsize(operations.len);
421 for (operations) |operation| numbering.updateSubtreeFingerprint(builder, operation);
422 }
423
424 fn updateOptionalOperation(
425 builder: *Builder,
426 numbering: *Numbering,
427 operation: ?*ir.Operation,
428 ) void {
429 if (operation) |value| {
430 builder.updateBool(true);
431 numbering.updateSubtreeFingerprint(builder, value);
432 } else {
433 builder.updateBool(false);
434 }
435 }
436
437 fn updateValue(builder: *Builder, numbering: *const Numbering, value: *const ir.Value) void {
438 if (numbering.valueId(value)) |id| {
439 builder.updateBool(true);
440 builder.updateU64(id);
441 } else {
442 builder.updateBool(false);
443 builder.updateU64(std.math.maxInt(u64));
444 }
445 }
446
447 fn updateRole(builder: *Builder, role: bufferization.BufferRole) void {
448 builder.updateBool(role.input);
449 builder.updateBool(role.output);
450 builder.updateBool(role.temporary);
451 builder.updateBool(role.constant);
452 }
453
454 fn dispatchCoverage() void {
455 coverage(fusion.FusionCluster, &.{ "ops", "kind" }, &.{});
456 coverage(fusion.FusionPlanAnalysis, &.{
457 "clusters", "elided", "fused_op_count", "max_cluster_len",
458 }, &.{"allocator"});
459 coverage(schedule_planning.ScheduleWorkItem, &.{
460 "id", "kind", "root", "ops", "output_value",
461 "dtype", "rank", "element_count", "resources",
462 }, &.{});
463 coverage(schedule_planning.ScheduleResourceEstimate, &.{
464 "element_count", "element_size", "op_count",
465 "external_input_value_count", "external_operand_count", "chain_operand_count",
466 "static_read_bytes", "static_write_bytes", "static_total_bytes",
467 "estimated_element_ops", "static_bytes_complete",
468 }, &.{});
469 coverage(schedule_planning.SchedulePlanAnalysis, &.{
470 "work_items", "single_work_count", "fusion_work_count",
471 "kernel_call_work_count", "scheduled_op_count", "total_static_elements",
472 }, &.{ "allocator", "root_to_item" });
473 }
474
475 fn memoryCoverage() void {
476 coverage(bufferization.BufferRole, &.{ "input", "output", "temporary", "constant" }, &.{});
477 coverage(bufferization.BufferSlot, &.{
478 "id", "value", "producer", "function", "role",
479 "dtype", "dims", "element_count", "row_major_strides", "byte_size",
480 }, &.{});
481 coverage(bufferization.FusionElision, &.{
482 "value", "producer", "root", "cluster_index",
483 }, &.{});
484 coverage(bufferization.BufferPlanAnalysis, &.{
485 "slots", "elisions", "value_to_slot",
486 "input_slot_count", "output_slot_count", "temporary_slot_count",
487 "constant_slot_count", "dynamic_slot_count", "total_static_bytes",
488 }, &.{ "allocator", "value_to_elision" });
489 coverage(memory_space.MemorySpaceAssignment, &.{
490 "slot_id", "value", "producer", "role", "space",
491 "access", "transfer", "byte_size", "output_source",
492 }, &.{});
493 coverage(memory_space.MemorySpacePlanAnalysis, &.{
494 "assignments", "host_slot_count", "device_global_slot_count",
495 "device_constant_slot_count", "device_shared_slot_count", "unified_slot_count",
496 "host_input_transfer_count", "host_output_transfer_count", "dynamic_slot_count",
497 "elided_value_count", "total_static_bytes",
498 }, &.{ "allocator", "slot_to_assignment" });
499 coverage(layout.LayoutAssignment, &.{
500 "slot_id", "value", "producer", "role", "dtype",
501 "memory_space", "kind", "rank", "dims", "element_strides",
502 "minor_to_major", "element_count", "byte_size", "element_size", "alignment",
503 "contiguous", "static_layout",
504 }, &.{});
505 coverage(layout.LayoutPlanAnalysis, &.{
506 "assignments", "scalar_layout_count", "row_major_layout_count",
507 "dynamic_row_major_layout_count", "host_slot_count", "device_global_slot_count",
508 "device_constant_slot_count", "device_shared_slot_count", "unified_slot_count",
509 "dynamic_slot_count", "elided_value_count", "total_static_bytes",
510 }, &.{ "allocator", "slot_to_assignment" });
511 }
512
513 fn kernelCoverage() void {
514 coverage(outline_model.KernelOutline, &.{
515 "id", "name", "kind", "work_item_id", "root",
516 "input_slot_ids", "output_slot_id", "element_count", "op_count",
517 }, &.{});
518 coverage(outline_model.KernelOutlinePlanAnalysis, &.{
519 "kernels", "total_input_slots", "total_scheduled_ops",
520 }, &.{ "allocator", "work_to_kernel" });
521 coverage(kernelization.LoweredKernel, &.{
522 "work_item_id", "entry_name", "program",
523 "argument_count", "body_fingerprint", "dynamic_shared_memory_bytes",
524 "schedule", "launch", "output_fill_pattern",
525 "scratch_fill_pattern", "body",
526 }, &.{});
527 coverage(kernelization.GeneratedSchedule, &.{ "kind", "threads" }, &.{});
528 coverage(@FieldType(kernelization.GeneratedSchedule, "threads"), &.{ "x", "y", "z" }, &.{});
529 const Launch = @typeInfo(@FieldType(kernelization.LoweredKernel, "launch")).optional.child;
530 coverage(Launch, &.{ "grid", "block" }, &.{});
531 coverage(kernelization.KernelizationAnalysis, &.{"kernels"}, &.{
532 "allocator", "context", "work_to_kernel",
533 });
534 }
535
536 fn coverage(
537 comptime Plan: type,
538 comptime summarized: []const []const u8,
539 comptime administrative: []const []const u8,
540 ) void {
541 @setEvalBranchQuota(100_000);
542 const names = @typeInfo(Plan).@"struct".field_names;
543 if (names.len != summarized.len + administrative.len) {
544 @compileError("stale plan summary field list: " ++ @typeName(Plan));
545 }
546 inline for (names) |name| {
547 if (!listed(summarized, name) and !listed(administrative, name)) {
548 @compileError("unsummarized plan field: " ++ @typeName(Plan) ++ "." ++ name);
549 }
550 }
551 }
552
553 fn listed(comptime names: []const []const u8, comptime name: []const u8) bool {
554 for (names) |candidate| {
555 if (std.mem.eql(u8, candidate, name)) return true;
556 }
557 return false;
558 }