master
1const std = @import("std");
2const Ast = std.zig.Ast;
3const Walk = @This();
4const assert = std.debug.assert;
5const BuiltinFn = std.zig.BuiltinFn;
6
7ast: *const Ast,
8transformations: *std.array_list.Managed(Transformation),
9unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
10in_scope_names: std.StringArrayHashMapUnmanaged(u32),
11replace_names: std.StringArrayHashMapUnmanaged(u32),
12gpa: std.mem.Allocator,
13arena: std.mem.Allocator,
14
15pub const Transformation = union(enum) {
16 /// Replace the fn decl AST Node with one whose body is only `@trap()` with
17 /// discarded parameters.
18 gut_function: Ast.Node.Index,
19 /// Omit a global declaration.
20 delete_node: Ast.Node.Index,
21 /// Delete a local variable declaration and replace all of its references
22 /// with `undefined`.
23 delete_var_decl: struct {
24 var_decl_node: Ast.Node.Index,
25 /// Identifier nodes that reference the variable.
26 references: std.ArrayList(Ast.Node.Index),
27 },
28 /// Replace an expression with `undefined`.
29 replace_with_undef: Ast.Node.Index,
30 /// Replace an expression with `true`.
31 replace_with_true: Ast.Node.Index,
32 /// Replace an expression with `false`.
33 replace_with_false: Ast.Node.Index,
34 /// Replace a node with another node.
35 replace_node: struct {
36 to_replace: Ast.Node.Index,
37 replacement: Ast.Node.Index,
38 },
39 /// Replace an `@import` with the imported file contents wrapped in a struct.
40 inline_imported_file: InlineImportedFile,
41
42 pub const InlineImportedFile = struct {
43 builtin_call_node: Ast.Node.Index,
44 imported_string: []const u8,
45 /// Identifier names that must be renamed in the inlined code or else
46 /// will cause ambiguous reference errors.
47 in_scope_names: std.StringArrayHashMapUnmanaged(void),
48 };
49};
50
51pub const Error = error{OutOfMemory};
52
53/// The result will be priority shuffled.
54pub fn findTransformations(
55 arena: std.mem.Allocator,
56 ast: *const Ast,
57 transformations: *std.array_list.Managed(Transformation),
58) !void {
59 transformations.clearRetainingCapacity();
60
61 var walk: Walk = .{
62 .ast = ast,
63 .transformations = transformations,
64 .gpa = transformations.allocator,
65 .arena = arena,
66 .unreferenced_globals = .{},
67 .in_scope_names = .{},
68 .replace_names = .{},
69 };
70 defer {
71 walk.unreferenced_globals.deinit(walk.gpa);
72 walk.in_scope_names.deinit(walk.gpa);
73 walk.replace_names.deinit(walk.gpa);
74 }
75
76 try walkMembers(&walk, walk.ast.rootDecls());
77
78 const unreferenced_globals = walk.unreferenced_globals.values();
79 try transformations.ensureUnusedCapacity(unreferenced_globals.len);
80 for (unreferenced_globals) |node| {
81 transformations.appendAssumeCapacity(.{ .delete_node = node });
82 }
83}
84
85fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void {
86 // First we scan for globals so that we can delete them while walking.
87 try scanDecls(w, members, .add);
88
89 for (members) |member| {
90 try walkMember(w, member);
91 }
92
93 try scanDecls(w, members, .remove);
94}
95
96const ScanDeclsAction = enum { add, remove };
97
98fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void {
99 const ast = w.ast;
100 const gpa = w.gpa;
101
102 for (members) |member_node| {
103 const name_token = switch (ast.nodeTag(member_node)) {
104 .global_var_decl,
105 .local_var_decl,
106 .simple_var_decl,
107 .aligned_var_decl,
108 => ast.nodeMainToken(member_node) + 1,
109
110 .fn_proto_simple,
111 .fn_proto_multi,
112 .fn_proto_one,
113 .fn_proto,
114 .fn_decl,
115 => ast.nodeMainToken(member_node) + 1,
116
117 else => continue,
118 };
119
120 assert(ast.tokenTag(name_token) == .identifier);
121 const name_bytes = ast.tokenSlice(name_token);
122
123 switch (action) {
124 .add => {
125 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
126
127 const gop = try w.in_scope_names.getOrPut(gpa, name_bytes);
128 if (!gop.found_existing) gop.value_ptr.* = 0;
129 gop.value_ptr.* += 1;
130 },
131 .remove => {
132 const entry = w.in_scope_names.getEntry(name_bytes).?;
133 if (entry.value_ptr.* <= 1) {
134 assert(w.in_scope_names.swapRemove(name_bytes));
135 } else {
136 entry.value_ptr.* -= 1;
137 }
138 },
139 }
140 }
141}
142
143fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
144 const ast = w.ast;
145 switch (ast.nodeTag(decl)) {
146 .fn_decl => {
147 const fn_proto, const body_node = ast.nodeData(decl).node_and_node;
148 try walkExpression(w, fn_proto);
149 if (!isFnBodyGutted(ast, body_node)) {
150 w.replace_names.clearRetainingCapacity();
151 try w.transformations.append(.{ .gut_function = decl });
152 try walkExpression(w, body_node);
153 }
154 },
155 .fn_proto_simple,
156 .fn_proto_multi,
157 .fn_proto_one,
158 .fn_proto,
159 => {
160 try walkExpression(w, decl);
161 },
162
163 .global_var_decl,
164 .local_var_decl,
165 .simple_var_decl,
166 .aligned_var_decl,
167 => try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?),
168
169 .test_decl => {
170 try w.transformations.append(.{ .delete_node = decl });
171 try walkExpression(w, ast.nodeData(decl).opt_token_and_node[1]);
172 },
173
174 .container_field_init,
175 .container_field_align,
176 .container_field,
177 => {
178 try w.transformations.append(.{ .delete_node = decl });
179 try walkContainerField(w, ast.fullContainerField(decl).?);
180 },
181
182 .@"comptime" => {
183 try w.transformations.append(.{ .delete_node = decl });
184 try walkExpression(w, decl);
185 },
186
187 .root => unreachable,
188 else => unreachable,
189 }
190}
191
192fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
193 const ast = w.ast;
194 switch (ast.nodeTag(node)) {
195 .identifier => {
196 const name_ident = ast.nodeMainToken(node);
197 assert(ast.tokenTag(name_ident) == .identifier);
198 const name_bytes = ast.tokenSlice(name_ident);
199 _ = w.unreferenced_globals.swapRemove(name_bytes);
200 if (w.replace_names.get(name_bytes)) |index| {
201 try w.transformations.items[index].delete_var_decl.references.append(w.arena, node);
202 }
203 },
204
205 .number_literal,
206 .char_literal,
207 .unreachable_literal,
208 .anyframe_literal,
209 .string_literal,
210 => {},
211
212 .multiline_string_literal => {},
213
214 .error_value => {},
215
216 .block_two,
217 .block_two_semicolon,
218 .block,
219 .block_semicolon,
220 => {
221 var buf: [2]Ast.Node.Index = undefined;
222 const statements = ast.blockStatements(&buf, node).?;
223 return walkBlock(w, node, statements);
224 },
225
226 .@"errdefer" => {
227 const expr = ast.nodeData(node).opt_token_and_node[1];
228 return walkExpression(w, expr);
229 },
230
231 .@"defer",
232 .@"comptime",
233 .@"nosuspend",
234 .@"suspend",
235 => {
236 return walkExpression(w, ast.nodeData(node).node);
237 },
238
239 .field_access => {
240 try walkExpression(w, ast.nodeData(node).node_and_token[0]);
241 },
242
243 .for_range => {
244 const start, const opt_end = ast.nodeData(node).node_and_opt_node;
245 try walkExpression(w, start);
246 if (opt_end.unwrap()) |end| {
247 return walkExpression(w, end);
248 }
249 },
250
251 .add,
252 .add_wrap,
253 .add_sat,
254 .array_cat,
255 .array_mult,
256 .assign,
257 .assign_bit_and,
258 .assign_bit_or,
259 .assign_shl,
260 .assign_shl_sat,
261 .assign_shr,
262 .assign_bit_xor,
263 .assign_div,
264 .assign_sub,
265 .assign_sub_wrap,
266 .assign_sub_sat,
267 .assign_mod,
268 .assign_add,
269 .assign_add_wrap,
270 .assign_add_sat,
271 .assign_mul,
272 .assign_mul_wrap,
273 .assign_mul_sat,
274 .bang_equal,
275 .bit_and,
276 .bit_or,
277 .shl,
278 .shl_sat,
279 .shr,
280 .bit_xor,
281 .bool_and,
282 .bool_or,
283 .div,
284 .equal_equal,
285 .greater_or_equal,
286 .greater_than,
287 .less_or_equal,
288 .less_than,
289 .merge_error_sets,
290 .mod,
291 .mul,
292 .mul_wrap,
293 .mul_sat,
294 .sub,
295 .sub_wrap,
296 .sub_sat,
297 .@"catch",
298 .error_union,
299 .switch_range,
300 .@"orelse",
301 .array_access,
302 => {
303 const lhs, const rhs = ast.nodeData(node).node_and_node;
304 try walkExpression(w, lhs);
305 try walkExpression(w, rhs);
306 },
307
308 .assign_destructure => {
309 const full = ast.assignDestructure(node);
310 for (full.ast.variables) |variable_node| {
311 switch (ast.nodeTag(variable_node)) {
312 .global_var_decl,
313 .local_var_decl,
314 .simple_var_decl,
315 .aligned_var_decl,
316 => try walkLocalVarDecl(w, ast.fullVarDecl(variable_node).?),
317
318 else => try walkExpression(w, variable_node),
319 }
320 }
321 return walkExpression(w, full.ast.value_expr);
322 },
323
324 .bit_not,
325 .bool_not,
326 .negation,
327 .negation_wrap,
328 .optional_type,
329 .address_of,
330 .@"try",
331 .@"resume",
332 .deref,
333 => {
334 return walkExpression(w, ast.nodeData(node).node);
335 },
336
337 .array_type,
338 .array_type_sentinel,
339 => {},
340
341 .ptr_type_aligned,
342 .ptr_type_sentinel,
343 .ptr_type,
344 .ptr_type_bit_range,
345 => {},
346
347 .array_init_one,
348 .array_init_one_comma,
349 .array_init_dot_two,
350 .array_init_dot_two_comma,
351 .array_init_dot,
352 .array_init_dot_comma,
353 .array_init,
354 .array_init_comma,
355 => {
356 var elements: [2]Ast.Node.Index = undefined;
357 return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
358 },
359
360 .struct_init_one,
361 .struct_init_one_comma,
362 .struct_init_dot_two,
363 .struct_init_dot_two_comma,
364 .struct_init_dot,
365 .struct_init_dot_comma,
366 .struct_init,
367 .struct_init_comma,
368 => {
369 var buf: [2]Ast.Node.Index = undefined;
370 return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
371 },
372
373 .call_one,
374 .call_one_comma,
375 .call,
376 .call_comma,
377 => {
378 var buf: [1]Ast.Node.Index = undefined;
379 return walkCall(w, ast.fullCall(&buf, node).?);
380 },
381
382 .slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
383
384 .unwrap_optional => {
385 try walkExpression(w, ast.nodeData(node).node_and_token[0]);
386 },
387
388 .@"break" => {
389 const label_token, const target = ast.nodeData(node).opt_token_and_opt_node;
390 if (label_token == .none and target == .none) {
391 // no expressions
392 } else if (label_token == .none and target != .none) {
393 try walkExpression(w, target.unwrap().?);
394 } else if (label_token != .none and target == .none) {
395 try walkIdentifier(w, label_token.unwrap().?);
396 } else if (label_token != .none and target != .none) {
397 try walkExpression(w, target.unwrap().?);
398 }
399 },
400
401 .@"continue" => {
402 const opt_label = ast.nodeData(node).opt_token_and_opt_node[0];
403 if (opt_label.unwrap()) |label| {
404 return walkIdentifier(w, label);
405 }
406 },
407
408 .@"return" => {
409 if (ast.nodeData(node).opt_node.unwrap()) |lhs| {
410 try walkExpression(w, lhs);
411 }
412 },
413
414 .grouped_expression => {
415 try walkExpression(w, ast.nodeData(node).node_and_token[0]);
416 },
417
418 .container_decl,
419 .container_decl_trailing,
420 .container_decl_arg,
421 .container_decl_arg_trailing,
422 .container_decl_two,
423 .container_decl_two_trailing,
424 .tagged_union,
425 .tagged_union_trailing,
426 .tagged_union_enum_tag,
427 .tagged_union_enum_tag_trailing,
428 .tagged_union_two,
429 .tagged_union_two_trailing,
430 => {
431 var buf: [2]Ast.Node.Index = undefined;
432 return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
433 },
434
435 .error_set_decl => {
436 const lbrace, const rbrace = ast.nodeData(node).token_and_token;
437
438 var i = lbrace + 1;
439 while (i < rbrace) : (i += 1) {
440 switch (ast.tokenTag(i)) {
441 .doc_comment => unreachable, // TODO
442 .identifier => try walkIdentifier(w, i),
443 .comma => {},
444 else => unreachable,
445 }
446 }
447 },
448
449 .builtin_call_two,
450 .builtin_call_two_comma,
451 .builtin_call,
452 .builtin_call_comma,
453 => {
454 var buf: [2]Ast.Node.Index = undefined;
455 const params = ast.builtinCallParams(&buf, node).?;
456 return walkBuiltinCall(w, node, params);
457 },
458
459 .fn_proto_simple,
460 .fn_proto_multi,
461 .fn_proto_one,
462 .fn_proto,
463 => {
464 var buf: [1]Ast.Node.Index = undefined;
465 return walkFnProto(w, ast.fullFnProto(&buf, node).?);
466 },
467
468 .anyframe_type => {
469 _, const child_type = ast.nodeData(node).token_and_node;
470 return walkExpression(w, child_type);
471 },
472
473 .@"switch",
474 .switch_comma,
475 => {
476 const full = ast.fullSwitch(node).?;
477 try walkExpression(w, full.ast.condition); // condition expression
478 try walkExpressions(w, full.ast.cases);
479 },
480
481 .switch_case_one,
482 .switch_case_inline_one,
483 .switch_case,
484 .switch_case_inline,
485 => return walkSwitchCase(w, ast.fullSwitchCase(node).?),
486
487 .while_simple,
488 .while_cont,
489 .@"while",
490 => return walkWhile(w, node, ast.fullWhile(node).?),
491
492 .for_simple,
493 .@"for",
494 => return walkFor(w, ast.fullFor(node).?),
495
496 .if_simple,
497 .@"if",
498 => return walkIf(w, node, ast.fullIf(node).?),
499
500 .asm_simple,
501 .@"asm",
502 => return walkAsm(w, ast.fullAsm(node).?),
503
504 .asm_legacy => {
505 return walkAsmLegacy(w, ast.legacyAsm(node).?);
506 },
507
508 .enum_literal => {
509 return walkIdentifier(w, ast.nodeMainToken(node)); // name
510 },
511
512 .fn_decl => unreachable,
513 .container_field => unreachable,
514 .container_field_init => unreachable,
515 .container_field_align => unreachable,
516 .root => unreachable,
517 .global_var_decl => unreachable,
518 .local_var_decl => unreachable,
519 .simple_var_decl => unreachable,
520 .aligned_var_decl => unreachable,
521 .test_decl => unreachable,
522 .asm_output => unreachable,
523 .asm_input => unreachable,
524 }
525}
526
527fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void {
528 _ = decl_node;
529
530 if (var_decl.ast.type_node.unwrap()) |type_node| {
531 try walkExpression(w, type_node);
532 }
533
534 if (var_decl.ast.align_node.unwrap()) |align_node| {
535 try walkExpression(w, align_node);
536 }
537
538 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
539 try walkExpression(w, addrspace_node);
540 }
541
542 if (var_decl.ast.section_node.unwrap()) |section_node| {
543 try walkExpression(w, section_node);
544 }
545
546 if (var_decl.ast.init_node.unwrap()) |init_node| {
547 if (!isUndefinedIdent(w.ast, init_node)) {
548 try w.transformations.append(.{ .replace_with_undef = init_node });
549 }
550 try walkExpression(w, init_node);
551 }
552}
553
554fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
555 try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name
556
557 if (var_decl.ast.type_node.unwrap()) |type_node| {
558 try walkExpression(w, type_node);
559 }
560
561 if (var_decl.ast.align_node.unwrap()) |align_node| {
562 try walkExpression(w, align_node);
563 }
564
565 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
566 try walkExpression(w, addrspace_node);
567 }
568
569 if (var_decl.ast.section_node.unwrap()) |section_node| {
570 try walkExpression(w, section_node);
571 }
572
573 if (var_decl.ast.init_node.unwrap()) |init_node| {
574 if (!isUndefinedIdent(w.ast, init_node)) {
575 try w.transformations.append(.{ .replace_with_undef = init_node });
576 }
577 try walkExpression(w, init_node);
578 }
579}
580
581fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
582 if (field.ast.type_expr.unwrap()) |type_expr| {
583 try walkExpression(w, type_expr); // type
584 }
585 if (field.ast.align_expr.unwrap()) |align_expr| {
586 try walkExpression(w, align_expr); // alignment
587 }
588 if (field.ast.value_expr.unwrap()) |value_expr| {
589 try walkExpression(w, value_expr); // value
590 }
591}
592
593fn walkBlock(
594 w: *Walk,
595 block_node: Ast.Node.Index,
596 statements: []const Ast.Node.Index,
597) Error!void {
598 _ = block_node;
599 const ast = w.ast;
600
601 for (statements) |stmt| {
602 switch (ast.nodeTag(stmt)) {
603 .global_var_decl,
604 .local_var_decl,
605 .simple_var_decl,
606 .aligned_var_decl,
607 => {
608 const var_decl = ast.fullVarDecl(stmt).?;
609 if (var_decl.ast.init_node != .none and
610 isUndefinedIdent(w.ast, var_decl.ast.init_node.unwrap().?))
611 {
612 try w.transformations.append(.{ .delete_var_decl = .{
613 .var_decl_node = stmt,
614 .references = .{},
615 } });
616 const name_tok = var_decl.ast.mut_token + 1;
617 const name_bytes = ast.tokenSlice(name_tok);
618 try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1));
619 } else {
620 try walkLocalVarDecl(w, var_decl);
621 }
622 },
623
624 else => {
625 switch (categorizeStmt(ast, stmt)) {
626 // Don't try to remove `_ = foo;` discards; those are handled separately.
627 .discard_identifier => {},
628 // definitely try to remove `_ = undefined;` though.
629 .discard_undefined, .trap_call, .other => {
630 try w.transformations.append(.{ .delete_node = stmt });
631 },
632 }
633 try walkExpression(w, stmt);
634 },
635 }
636 }
637}
638
639fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
640 try walkExpression(w, array_type.ast.elem_count);
641 if (array_type.ast.sentinel.unwrap()) |sentinel| {
642 try walkExpression(w, sentinel);
643 }
644 return walkExpression(w, array_type.ast.elem_type);
645}
646
647fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
648 if (array_init.ast.type_expr.unwrap()) |type_expr| {
649 try walkExpression(w, type_expr); // T
650 }
651 for (array_init.ast.elements) |elem_init| {
652 try walkExpression(w, elem_init);
653 }
654}
655
656fn walkStructInit(
657 w: *Walk,
658 struct_node: Ast.Node.Index,
659 struct_init: Ast.full.StructInit,
660) Error!void {
661 _ = struct_node;
662 if (struct_init.ast.type_expr.unwrap()) |type_expr| {
663 try walkExpression(w, type_expr); // T
664 }
665 for (struct_init.ast.fields) |field_init| {
666 try walkExpression(w, field_init);
667 }
668}
669
670fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
671 try walkExpression(w, call.ast.fn_expr);
672 try walkExpressions(w, call.ast.params);
673}
674
675fn walkSlice(
676 w: *Walk,
677 slice_node: Ast.Node.Index,
678 slice: Ast.full.Slice,
679) Error!void {
680 _ = slice_node;
681 try walkExpression(w, slice.ast.sliced);
682 try walkExpression(w, slice.ast.start);
683 if (slice.ast.end.unwrap()) |end| {
684 try walkExpression(w, end);
685 }
686 if (slice.ast.sentinel.unwrap()) |sentinel| {
687 try walkExpression(w, sentinel);
688 }
689}
690
691fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
692 const ast = w.ast;
693 assert(ast.tokenTag(name_ident) == .identifier);
694 const name_bytes = ast.tokenSlice(name_ident);
695 _ = w.unreferenced_globals.swapRemove(name_bytes);
696}
697
698fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
699 _ = w;
700 _ = name_ident;
701}
702
703fn walkContainerDecl(
704 w: *Walk,
705 container_decl_node: Ast.Node.Index,
706 container_decl: Ast.full.ContainerDecl,
707) Error!void {
708 _ = container_decl_node;
709 if (container_decl.ast.arg.unwrap()) |arg| {
710 try walkExpression(w, arg);
711 }
712 try walkMembers(w, container_decl.ast.members);
713}
714
715fn walkBuiltinCall(
716 w: *Walk,
717 call_node: Ast.Node.Index,
718 params: []const Ast.Node.Index,
719) Error!void {
720 const ast = w.ast;
721 const builtin_token = ast.nodeMainToken(call_node);
722 const builtin_name = ast.tokenSlice(builtin_token);
723 const info = BuiltinFn.list.get(builtin_name).?;
724 switch (info.tag) {
725 .import => {
726 const operand_node = params[0];
727 const str_lit_token = ast.nodeMainToken(operand_node);
728 const token_bytes = ast.tokenSlice(str_lit_token);
729 if (std.mem.endsWith(u8, token_bytes, ".zig\"")) {
730 const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch
731 unreachable;
732 try w.transformations.append(.{ .inline_imported_file = .{
733 .builtin_call_node = call_node,
734 .imported_string = imported_string,
735 .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init(
736 w.arena,
737 w.in_scope_names.keys(),
738 &.{},
739 ),
740 } });
741 }
742 },
743 else => {},
744 }
745 for (params) |param_node| {
746 try walkExpression(w, param_node);
747 }
748}
749
750fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
751 const ast = w.ast;
752
753 {
754 var it = fn_proto.iterate(ast);
755 while (it.next()) |param| {
756 if (param.type_expr) |type_expr| {
757 try walkExpression(w, type_expr);
758 }
759 }
760 }
761
762 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
763 try walkExpression(w, align_expr);
764 }
765
766 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
767 try walkExpression(w, addrspace_expr);
768 }
769
770 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
771 try walkExpression(w, section_expr);
772 }
773
774 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
775 try walkExpression(w, callconv_expr);
776 }
777
778 const return_type = fn_proto.ast.return_type.unwrap().?;
779 try walkExpression(w, return_type);
780}
781
782fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
783 for (expressions) |expression| {
784 try walkExpression(w, expression);
785 }
786}
787
788fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
789 for (switch_case.ast.values) |value_expr| {
790 try walkExpression(w, value_expr);
791 }
792 try walkExpression(w, switch_case.ast.target_expr);
793}
794
795fn walkWhile(w: *Walk, node_index: Ast.Node.Index, while_node: Ast.full.While) Error!void {
796 // Perform these transformations in this priority order:
797 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
798 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
799 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
800 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
801 if (!isTrueIdent(w.ast, while_node.ast.cond_expr) and
802 (while_node.ast.else_expr == .none or isEmptyBlock(w.ast, while_node.ast.else_expr.unwrap().?)))
803 {
804 try w.transformations.ensureUnusedCapacity(1);
805 w.transformations.appendAssumeCapacity(.{ .replace_with_true = while_node.ast.cond_expr });
806 } else if (!isFalseIdent(w.ast, while_node.ast.cond_expr) and isEmptyBlock(w.ast, while_node.ast.then_expr)) {
807 try w.transformations.ensureUnusedCapacity(1);
808 w.transformations.appendAssumeCapacity(.{ .replace_with_false = while_node.ast.cond_expr });
809 } else if (isTrueIdent(w.ast, while_node.ast.cond_expr)) {
810 try w.transformations.ensureUnusedCapacity(1);
811 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
812 .to_replace = node_index,
813 .replacement = while_node.ast.then_expr,
814 } });
815 } else if (isFalseIdent(w.ast, while_node.ast.cond_expr)) {
816 try w.transformations.ensureUnusedCapacity(1);
817 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
818 .to_replace = node_index,
819 .replacement = while_node.ast.else_expr.unwrap().?,
820 } });
821 }
822
823 try walkExpression(w, while_node.ast.cond_expr); // condition
824
825 if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
826 try walkExpression(w, cont_expr);
827 }
828
829 try walkExpression(w, while_node.ast.then_expr);
830
831 if (while_node.ast.else_expr.unwrap()) |else_expr| {
832 try walkExpression(w, else_expr);
833 }
834}
835
836fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
837 try walkExpressions(w, for_node.ast.inputs);
838 try walkExpression(w, for_node.ast.then_expr);
839 if (for_node.ast.else_expr.unwrap()) |else_expr| {
840 try walkExpression(w, else_expr);
841 }
842}
843
844fn walkIf(w: *Walk, node_index: Ast.Node.Index, if_node: Ast.full.If) Error!void {
845 // Perform these transformations in this priority order:
846 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
847 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
848 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
849 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
850 if (!isTrueIdent(w.ast, if_node.ast.cond_expr) and
851 (if_node.ast.else_expr == .none or isEmptyBlock(w.ast, if_node.ast.else_expr.unwrap().?)))
852 {
853 try w.transformations.ensureUnusedCapacity(1);
854 w.transformations.appendAssumeCapacity(.{ .replace_with_true = if_node.ast.cond_expr });
855 } else if (!isFalseIdent(w.ast, if_node.ast.cond_expr) and isEmptyBlock(w.ast, if_node.ast.then_expr)) {
856 try w.transformations.ensureUnusedCapacity(1);
857 w.transformations.appendAssumeCapacity(.{ .replace_with_false = if_node.ast.cond_expr });
858 } else if (isTrueIdent(w.ast, if_node.ast.cond_expr)) {
859 try w.transformations.ensureUnusedCapacity(1);
860 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
861 .to_replace = node_index,
862 .replacement = if_node.ast.then_expr,
863 } });
864 } else if (isFalseIdent(w.ast, if_node.ast.cond_expr)) {
865 try w.transformations.ensureUnusedCapacity(1);
866 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
867 .to_replace = node_index,
868 .replacement = if_node.ast.else_expr.unwrap().?,
869 } });
870 }
871
872 try walkExpression(w, if_node.ast.cond_expr); // condition
873 try walkExpression(w, if_node.ast.then_expr);
874 if (if_node.ast.else_expr.unwrap()) |else_expr| {
875 try walkExpression(w, else_expr);
876 }
877}
878
879fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
880 try walkExpression(w, asm_node.ast.template);
881 try walkExpressions(w, asm_node.ast.items);
882}
883
884fn walkAsmLegacy(w: *Walk, asm_node: Ast.full.AsmLegacy) Error!void {
885 try walkExpression(w, asm_node.ast.template);
886 try walkExpressions(w, asm_node.ast.items);
887}
888
889/// Check if it is already gutted (i.e. its body replaced with `@trap()`).
890fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
891 // skip over discards
892 var statements_buf: [2]Ast.Node.Index = undefined;
893 const statements = switch (ast.nodeTag(body_node)) {
894 .block_two,
895 .block_two_semicolon,
896 .block,
897 .block_semicolon,
898 => ast.blockStatements(&statements_buf, body_node).?,
899
900 else => return false,
901 };
902 var i: usize = 0;
903 while (i < statements.len) : (i += 1) {
904 switch (categorizeStmt(ast, statements[i])) {
905 .discard_identifier => continue,
906 .trap_call => return i + 1 == statements.len,
907 else => return false,
908 }
909 }
910 return false;
911}
912
913const StmtCategory = enum {
914 discard_undefined,
915 discard_identifier,
916 trap_call,
917 other,
918};
919
920fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
921 switch (ast.nodeTag(stmt)) {
922 .builtin_call_two,
923 .builtin_call_two_comma,
924 .builtin_call,
925 .builtin_call_comma,
926 => {
927 var buf: [2]Ast.Node.Index = undefined;
928 const params = ast.builtinCallParams(&buf, stmt).?;
929 return categorizeBuiltinCall(ast, ast.nodeMainToken(stmt), params);
930 },
931 .assign => {
932 const lhs, const rhs = ast.nodeData(stmt).node_and_node;
933 if (isDiscardIdent(ast, lhs) and ast.nodeTag(rhs) == .identifier) {
934 const name_bytes = ast.tokenSlice(ast.nodeMainToken(rhs));
935 if (std.mem.eql(u8, name_bytes, "undefined")) {
936 return .discard_undefined;
937 } else {
938 return .discard_identifier;
939 }
940 }
941 return .other;
942 },
943 else => return .other,
944 }
945}
946
947fn categorizeBuiltinCall(
948 ast: *const Ast,
949 builtin_token: Ast.TokenIndex,
950 params: []const Ast.Node.Index,
951) StmtCategory {
952 if (params.len != 0) return .other;
953 const name_bytes = ast.tokenSlice(builtin_token);
954 if (std.mem.eql(u8, name_bytes, "@trap"))
955 return .trap_call;
956 return .other;
957}
958
959fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
960 return isMatchingIdent(ast, node, "_");
961}
962
963fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
964 return isMatchingIdent(ast, node, "undefined");
965}
966
967fn isTrueIdent(ast: *const Ast, node: Ast.Node.Index) bool {
968 return isMatchingIdent(ast, node, "true");
969}
970
971fn isFalseIdent(ast: *const Ast, node: Ast.Node.Index) bool {
972 return isMatchingIdent(ast, node, "false");
973}
974
975fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
976 switch (ast.nodeTag(node)) {
977 .identifier => {
978 const token_index = ast.nodeMainToken(node);
979 const name_bytes = ast.tokenSlice(token_index);
980 return std.mem.eql(u8, name_bytes, string);
981 },
982 else => return false,
983 }
984}
985
986fn isEmptyBlock(ast: *const Ast, node: Ast.Node.Index) bool {
987 switch (ast.nodeTag(node)) {
988 .block_two => {
989 const opt_lhs, const opt_rhs = ast.nodeData(node).opt_node_and_opt_node;
990 return opt_lhs == .none and opt_rhs == .none;
991 },
992 else => return false,
993 }
994}