Walk.zig (32099B) - Raw
1 const std = @import("std"); 2 const Ast = std.zig.Ast; 3 const Walk = @This(); 4 const assert = std.debug.assert; 5 const BuiltinFn = std.zig.BuiltinFn; 6 7 ast: *const Ast, 8 transformations: *std.array_list.Managed(Transformation), 9 unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index), 10 in_scope_names: std.StringArrayHashMapUnmanaged(u32), 11 replace_names: std.StringArrayHashMapUnmanaged(u32), 12 gpa: std.mem.Allocator, 13 arena: std.mem.Allocator, 14 15 pub 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.ArrayListUnmanaged(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 51 pub const Error = error{OutOfMemory}; 52 53 /// The result will be priority shuffled. 54 pub 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 85 fn 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 96 const ScanDeclsAction = enum { add, remove }; 97 98 fn 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 143 fn 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 192 fn 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 527 fn 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 554 fn 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 581 fn 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 593 fn 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 639 fn 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 647 fn 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 656 fn 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 670 fn 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 675 fn 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 691 fn 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 698 fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void { 699 _ = w; 700 _ = name_ident; 701 } 702 703 fn 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 715 fn 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 750 fn 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 782 fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void { 783 for (expressions) |expression| { 784 try walkExpression(w, expression); 785 } 786 } 787 788 fn 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 795 fn 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 836 fn 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 844 fn 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 879 fn 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 884 fn 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()`). 890 fn 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 913 const StmtCategory = enum { 914 discard_undefined, 915 discard_identifier, 916 trap_call, 917 other, 918 }; 919 920 fn 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 947 fn 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 959 fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool { 960 return isMatchingIdent(ast, node, "_"); 961 } 962 963 fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool { 964 return isMatchingIdent(ast, node, "undefined"); 965 } 966 967 fn isTrueIdent(ast: *const Ast, node: Ast.Node.Index) bool { 968 return isMatchingIdent(ast, node, "true"); 969 } 970 971 fn isFalseIdent(ast: *const Ast, node: Ast.Node.Index) bool { 972 return isMatchingIdent(ast, node, "false"); 973 } 974 975 fn 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 986 fn 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 }