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.
This commit is contained in:
Andrew Kelley 2023-11-03 21:31:32 -07:00
parent 6aedddf900
commit a2f4adbd19
3 changed files with 103 additions and 12 deletions

View File

@ -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 => {

View File

@ -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, {});
},
};
}

View File

@ -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(