From a2f4adbd19ffea051a4fa0318bb0154e07660e55 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Nov 2023 21:31:32 -0700 Subject: [PATCH] zig reduce: add "delete unused globals" transform While walking the AST looking for reductions, notice if any globals are unreferenced, and if they are, add a transformation for removing the global. --- lib/std/zig/render.zig | 8 +++- src/reduce.zig | 7 ++- src/reduce/Walk.zig | 100 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 12 deletions(-) diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index c2ee1bbf22..0578b39ff1 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -22,20 +22,25 @@ pub const Fixups = struct { /// with a function body of `@trap()` instead, with all parameters /// discarded. gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{}, + /// These global declarations will be omitted. + omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{}, pub fn count(f: Fixups) usize { return f.unused_var_decls.count() + - f.gut_functions.count(); + f.gut_functions.count() + + f.omit_nodes.count(); } pub fn clearRetainingCapacity(f: *Fixups) void { f.unused_var_decls.clearRetainingCapacity(); f.gut_functions.clearRetainingCapacity(); + f.omit_nodes.clearRetainingCapacity(); } pub fn deinit(f: *Fixups, gpa: Allocator) void { f.unused_var_decls.deinit(gpa); f.gut_functions.deinit(gpa); + f.omit_nodes.deinit(gpa); f.* = undefined; } }; @@ -114,6 +119,7 @@ fn renderMember( const token_tags = tree.tokens.items(.tag); const main_tokens = tree.nodes.items(.main_token); const datas = tree.nodes.items(.data); + if (r.fixups.omit_nodes.contains(decl)) return; try renderDocComments(r, tree.firstToken(decl)); switch (tree.nodes.items(.tag)[decl]) { .fn_decl => { diff --git a/src/reduce.zig b/src/reduce.zig index b8d4affd2a..b882d3a283 100644 --- a/src/reduce.zig +++ b/src/reduce.zig @@ -166,8 +166,8 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { try std.fs.cwd().writeFile(root_source_file_path, rendered.items); const interestingness = try runCheck(arena, interestingness_argv.items); - std.debug.print("{d} random transformations: {s}\n", .{ - subset_size, @tagName(interestingness), + std.debug.print("{d} random transformations: {s}. {d} remaining\n", .{ + subset_size, @tagName(interestingness), transformations.items.len - start_index, }); switch (interestingness) { .interesting => { @@ -250,6 +250,9 @@ fn transformationsToFixups( .gut_function => |fn_decl_node| { try fixups.gut_functions.put(gpa, fn_decl_node, {}); }, + .delete_node => |decl_node| { + try fixups.omit_nodes.put(gpa, decl_node, {}); + }, }; } diff --git a/src/reduce/Walk.zig b/src/reduce/Walk.zig index ea6528c917..a537e86762 100644 --- a/src/reduce/Walk.zig +++ b/src/reduce/Walk.zig @@ -5,11 +5,15 @@ const assert = std.debug.assert; ast: *const Ast, transformations: *std.ArrayList(Transformation), +unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index), +gpa: std.mem.Allocator, pub const Transformation = union(enum) { /// Replace the fn decl AST Node with one whose body is only `@trap()` with /// discarded parameters. gut_function: Ast.Node.Index, + /// Omit a global declaration. + delete_node: Ast.Node.Index, }; pub const Error = error{OutOfMemory}; @@ -21,16 +25,59 @@ pub fn findTransformations(ast: *const Ast, transformations: *std.ArrayList(Tran var walk: Walk = .{ .ast = ast, .transformations = transformations, + .gpa = transformations.allocator, + .unreferenced_globals = .{}, }; + defer walk.unreferenced_globals.deinit(walk.gpa); + try walkMembers(&walk, walk.ast.rootDecls()); + + const unreferenced_globals = walk.unreferenced_globals.values(); + try transformations.ensureUnusedCapacity(unreferenced_globals.len); + for (unreferenced_globals) |node| { + transformations.appendAssumeCapacity(.{ .delete_node = node }); + } } fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void { + // First we scan for globals so that we can delete them while walking. + try scanDecls(w, members); + for (members) |member| { try walkMember(w, member); } } +fn scanDecls(w: *Walk, members: []const Ast.Node.Index) Error!void { + const ast = w.ast; + const gpa = w.gpa; + const node_tags = ast.nodes.items(.tag); + const main_tokens = ast.nodes.items(.main_token); + const token_tags = ast.tokens.items(.tag); + + for (members) |member_node| { + const name_token = switch (node_tags[member_node]) { + .global_var_decl, + .local_var_decl, + .simple_var_decl, + .aligned_var_decl, + => main_tokens[member_node] + 1, + + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + .fn_decl, + => main_tokens[member_node] + 1, + + else => continue, + }; + assert(token_tags[name_token] == .identifier); + const name_bytes = ast.tokenSlice(name_token); + try w.unreferenced_globals.put(gpa, name_bytes, member_node); + } +} + fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void { const ast = w.ast; const datas = ast.nodes.items(.data); @@ -53,6 +100,7 @@ fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void { }, .@"usingnamespace" => { + try w.transformations.append(.{ .delete_node = decl }); const expr = datas[decl].lhs; try walkExpression(w, expr); }, @@ -61,9 +109,10 @@ fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void { .local_var_decl, .simple_var_decl, .aligned_var_decl, - => try walkVarDecl(w, ast.fullVarDecl(decl).?), + => try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?), .test_decl => { + try w.transformations.append(.{ .delete_node = decl }); try walkExpression(w, datas[decl].rhs); }, @@ -72,7 +121,10 @@ fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void { .container_field, => try walkContainerField(w, ast.fullContainerField(decl).?), - .@"comptime" => try walkExpression(w, decl), + .@"comptime" => { + try w.transformations.append(.{ .delete_node = decl }); + try walkExpression(w, decl); + }, .root => unreachable, else => unreachable, @@ -86,7 +138,7 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void { const node_tags = ast.nodes.items(.tag); const datas = ast.nodes.items(.data); switch (node_tags[node]) { - .identifier => {}, + .identifier => try walkIdentifier(w, main_tokens[node]), .number_literal, .char_literal, @@ -463,8 +515,32 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void { } } +fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void { + _ = decl_node; + + if (var_decl.ast.type_node != 0) { + try walkExpression(w, var_decl.ast.type_node); + } + + if (var_decl.ast.align_node != 0) { + try walkExpression(w, var_decl.ast.align_node); + } + + if (var_decl.ast.addrspace_node != 0) { + try walkExpression(w, var_decl.ast.addrspace_node); + } + + if (var_decl.ast.section_node != 0) { + try walkExpression(w, var_decl.ast.section_node); + } + + assert(var_decl.ast.init_node != 0); + + return walkExpression(w, var_decl.ast.init_node); +} + fn walkVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void { - try walkIdentifier(w, var_decl.ast.mut_token + 1); // name + try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name if (var_decl.ast.type_node != 0) { try walkExpression(w, var_decl.ast.type_node); @@ -571,9 +647,17 @@ fn walkSlice( } } -fn walkIdentifier(w: *Walk, token_index: Ast.TokenIndex) Error!void { +fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void { + const ast = w.ast; + const token_tags = ast.tokens.items(.tag); + assert(token_tags[name_ident] == .identifier); + const name_bytes = ast.tokenSlice(name_ident); + _ = w.unreferenced_globals.swapRemove(name_bytes); +} + +fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void { _ = w; - _ = token_index; + _ = name_ident; } fn walkContainerDecl( @@ -585,9 +669,7 @@ fn walkContainerDecl( if (container_decl.ast.arg != 0) { try walkExpression(w, container_decl.ast.arg); } - for (container_decl.ast.members) |member| { - try walkMember(w, member); - } + try walkMembers(w, container_decl.ast.members); } fn walkBuiltinCall(