authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-09 00:52:38-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-11-09 00:52:38-07:00
logb2ed2c4d4fcc91980118e875b753eb82c7061bb7
treeed85afbfdf2f35e7872e5265817a47842acc589f
parentd99bed1b10f85f2becca2a1c2587e1c2cb1968e6
parentdb785e25b9808168d6259de53ffb4151a422e307
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17888 from AdamGoertz/zig-reduce

zig-reduce: Add reductions for `if` and `while`

3 files changed, 117 insertions(+), 15 deletions(-)

lib/std/zig/render.zig+12-5
...@@ -25,7 +25,9 @@ pub const Fixups = struct {...@@ -25,7 +25,9 @@ pub const Fixups = struct {
25 /// These global declarations will be omitted.25 /// These global declarations will be omitted.
26 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},26 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
27 /// These expressions will be replaced with the string value.27 /// These expressions will be replaced with the string value.
28 replace_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{},28 replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{},
29 /// These nodes will be replaced with a different node.
30 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .{},
29 /// Change all identifier names matching the key to be value instead.31 /// Change all identifier names matching the key to be value instead.
30 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .{},32 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .{},
3133
...@@ -37,7 +39,8 @@ pub const Fixups = struct {...@@ -37,7 +39,8 @@ pub const Fixups = struct {
37 return f.unused_var_decls.count() +39 return f.unused_var_decls.count() +
38 f.gut_functions.count() +40 f.gut_functions.count() +
39 f.omit_nodes.count() +41 f.omit_nodes.count() +
40 f.replace_nodes.count() +42 f.replace_nodes_with_string.count() +
43 f.replace_nodes_with_node.count() +
41 f.rename_identifiers.count() +44 f.rename_identifiers.count() +
42 @intFromBool(f.rebase_imported_paths != null);45 @intFromBool(f.rebase_imported_paths != null);
43 }46 }
...@@ -46,7 +49,8 @@ pub const Fixups = struct {...@@ -46,7 +49,8 @@ pub const Fixups = struct {
46 f.unused_var_decls.clearRetainingCapacity();49 f.unused_var_decls.clearRetainingCapacity();
47 f.gut_functions.clearRetainingCapacity();50 f.gut_functions.clearRetainingCapacity();
48 f.omit_nodes.clearRetainingCapacity();51 f.omit_nodes.clearRetainingCapacity();
49 f.replace_nodes.clearRetainingCapacity();52 f.replace_nodes_with_string.clearRetainingCapacity();
53 f.replace_nodes_with_node.clearRetainingCapacity();
50 f.rename_identifiers.clearRetainingCapacity();54 f.rename_identifiers.clearRetainingCapacity();
5155
52 f.rebase_imported_paths = null;56 f.rebase_imported_paths = null;
...@@ -56,7 +60,8 @@ pub const Fixups = struct {...@@ -56,7 +60,8 @@ pub const Fixups = struct {
56 f.unused_var_decls.deinit(gpa);60 f.unused_var_decls.deinit(gpa);
57 f.gut_functions.deinit(gpa);61 f.gut_functions.deinit(gpa);
58 f.omit_nodes.deinit(gpa);62 f.omit_nodes.deinit(gpa);
59 f.replace_nodes.deinit(gpa);63 f.replace_nodes_with_string.deinit(gpa);
64 f.replace_nodes_with_node.deinit(gpa);
60 f.rename_identifiers.deinit(gpa);65 f.rename_identifiers.deinit(gpa);
61 f.* = undefined;66 f.* = undefined;
62 }67 }
...@@ -329,10 +334,12 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {...@@ -329,10 +334,12 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
329 const main_tokens = tree.nodes.items(.main_token);334 const main_tokens = tree.nodes.items(.main_token);
330 const node_tags = tree.nodes.items(.tag);335 const node_tags = tree.nodes.items(.tag);
331 const datas = tree.nodes.items(.data);336 const datas = tree.nodes.items(.data);
332 if (r.fixups.replace_nodes.get(node)) |replacement| {337 if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
333 try ais.writer().writeAll(replacement);338 try ais.writer().writeAll(replacement);
334 try renderOnlySpace(r, space);339 try renderOnlySpace(r, space);
335 return;340 return;
341 } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
342 return renderExpression(r, replacement, space);
336 }343 }
337 switch (node_tags[node]) {344 switch (node_tags[node]) {
338 .identifier => {345 .identifier => {
src/reduce.zig+13-4
...@@ -226,7 +226,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -226,7 +226,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
226 }226 }
227227
228 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);228 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
229 //std.debug.print("trying this code:\n{s}\n", .{rendered.items});229 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
230230
231 const interestingness = try runCheck(arena, interestingness_argv.items);231 const interestingness = try runCheck(arena, interestingness_argv.items);
232 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{232 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
...@@ -324,11 +324,20 @@ fn transformationsToFixups(...@@ -324,11 +324,20 @@ fn transformationsToFixups(
324 .delete_var_decl => |delete_var_decl| {324 .delete_var_decl => |delete_var_decl| {
325 try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {});325 try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {});
326 for (delete_var_decl.references.items) |ident_node| {326 for (delete_var_decl.references.items) |ident_node| {
327 try fixups.replace_nodes.put(gpa, ident_node, "undefined");327 try fixups.replace_nodes_with_string.put(gpa, ident_node, "undefined");
328 }328 }
329 },329 },
330 .replace_with_undef => |node| {330 .replace_with_undef => |node| {
331 try fixups.replace_nodes.put(gpa, node, "undefined");331 try fixups.replace_nodes_with_string.put(gpa, node, "undefined");
332 },
333 .replace_with_true => |node| {
334 try fixups.replace_nodes_with_string.put(gpa, node, "true");
335 },
336 .replace_with_false => |node| {
337 try fixups.replace_nodes_with_string.put(gpa, node, "false");
338 },
339 .replace_node => |r| {
340 try fixups.replace_nodes_with_node.put(gpa, r.to_replace, r.replacement);
332 },341 },
333 .inline_imported_file => |inline_imported_file| {342 .inline_imported_file => |inline_imported_file| {
334 const full_imported_path = try std.fs.path.join(gpa, &.{343 const full_imported_path = try std.fs.path.join(gpa, &.{
...@@ -371,7 +380,7 @@ fn transformationsToFixups(...@@ -371,7 +380,7 @@ fn transformationsToFixups(
371 try other_file_ast.renderToArrayList(&other_source, inlined_fixups);380 try other_file_ast.renderToArrayList(&other_source, inlined_fixups);
372 try other_source.appendSlice("}");381 try other_source.appendSlice("}");
373382
374 try fixups.replace_nodes.put(383 try fixups.replace_nodes_with_string.put(
375 gpa,384 gpa,
376 inline_imported_file.builtin_call_node,385 inline_imported_file.builtin_call_node,
377 try arena.dupe(u8, other_source.items),386 try arena.dupe(u8, other_source.items),
src/reduce/Walk.zig+92-6
...@@ -27,6 +27,15 @@ pub const Transformation = union(enum) {...@@ -27,6 +27,15 @@ pub const Transformation = union(enum) {
27 },27 },
28 /// Replace an expression with `undefined`.28 /// Replace an expression with `undefined`.
29 replace_with_undef: Ast.Node.Index,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 },
30 /// Replace an `@import` with the imported file contents wrapped in a struct.39 /// Replace an `@import` with the imported file contents wrapped in a struct.
31 inline_imported_file: InlineImportedFile,40 inline_imported_file: InlineImportedFile,
3241
...@@ -550,7 +559,7 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {...@@ -550,7 +559,7 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
550 .while_simple,559 .while_simple,
551 .while_cont,560 .while_cont,
552 .@"while",561 .@"while",
553 => return walkWhile(w, ast.fullWhile(node).?),562 => return walkWhile(w, node, ast.fullWhile(node).?),
554563
555 .for_simple,564 .for_simple,
556 .@"for",565 .@"for",
...@@ -558,7 +567,7 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {...@@ -558,7 +567,7 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
558567
559 .if_simple,568 .if_simple,
560 .@"if",569 .@"if",
561 => return walkIf(w, ast.fullIf(node).?),570 => return walkIf(w, node, ast.fullIf(node).?),
562571
563 .asm_simple,572 .asm_simple,
564 .@"asm",573 .@"asm",
...@@ -854,15 +863,43 @@ fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {...@@ -854,15 +863,43 @@ fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
854 try walkExpression(w, switch_case.ast.target_expr);863 try walkExpression(w, switch_case.ast.target_expr);
855}864}
856865
857fn walkWhile(w: *Walk, while_node: Ast.full.While) Error!void {866fn walkWhile(w: *Walk, node_index: Ast.Node.Index, while_node: Ast.full.While) Error!void {
867 assert(while_node.ast.cond_expr != 0);
868 assert(while_node.ast.then_expr != 0);
869
870 // Perform these transformations in this priority order:
871 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
872 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
873 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
874 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
875 if (!isTrueIdent(w.ast, while_node.ast.cond_expr) and
876 (while_node.ast.else_expr == 0 or isEmptyBlock(w.ast, while_node.ast.else_expr)))
877 {
878 try w.transformations.ensureUnusedCapacity(1);
879 w.transformations.appendAssumeCapacity(.{ .replace_with_true = while_node.ast.cond_expr });
880 } else if (!isFalseIdent(w.ast, while_node.ast.cond_expr) and isEmptyBlock(w.ast, while_node.ast.then_expr)) {
881 try w.transformations.ensureUnusedCapacity(1);
882 w.transformations.appendAssumeCapacity(.{ .replace_with_false = while_node.ast.cond_expr });
883 } else if (isTrueIdent(w.ast, while_node.ast.cond_expr)) {
884 try w.transformations.ensureUnusedCapacity(1);
885 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
886 .to_replace = node_index,
887 .replacement = while_node.ast.then_expr,
888 } });
889 } else if (isFalseIdent(w.ast, while_node.ast.cond_expr)) {
890 try w.transformations.ensureUnusedCapacity(1);
891 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
892 .to_replace = node_index,
893 .replacement = while_node.ast.else_expr,
894 } });
895 }
896
858 try walkExpression(w, while_node.ast.cond_expr); // condition897 try walkExpression(w, while_node.ast.cond_expr); // condition
859898
860 if (while_node.ast.cont_expr != 0) {899 if (while_node.ast.cont_expr != 0) {
861 try walkExpression(w, while_node.ast.cont_expr);900 try walkExpression(w, while_node.ast.cont_expr);
862 }901 }
863902
864 try walkExpression(w, while_node.ast.cond_expr); // condition
865
866 if (while_node.ast.then_expr != 0) {903 if (while_node.ast.then_expr != 0) {
867 try walkExpression(w, while_node.ast.then_expr);904 try walkExpression(w, while_node.ast.then_expr);
868 }905 }
...@@ -881,7 +918,37 @@ fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {...@@ -881,7 +918,37 @@ fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
881 }918 }
882}919}
883920
884fn walkIf(w: *Walk, if_node: Ast.full.If) Error!void {921fn walkIf(w: *Walk, node_index: Ast.Node.Index, if_node: Ast.full.If) Error!void {
922 assert(if_node.ast.cond_expr != 0);
923 assert(if_node.ast.then_expr != 0);
924
925 // Perform these transformations in this priority order:
926 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
927 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
928 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
929 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
930 if (!isTrueIdent(w.ast, if_node.ast.cond_expr) and
931 (if_node.ast.else_expr == 0 or isEmptyBlock(w.ast, if_node.ast.else_expr)))
932 {
933 try w.transformations.ensureUnusedCapacity(1);
934 w.transformations.appendAssumeCapacity(.{ .replace_with_true = if_node.ast.cond_expr });
935 } else if (!isFalseIdent(w.ast, if_node.ast.cond_expr) and isEmptyBlock(w.ast, if_node.ast.then_expr)) {
936 try w.transformations.ensureUnusedCapacity(1);
937 w.transformations.appendAssumeCapacity(.{ .replace_with_false = if_node.ast.cond_expr });
938 } else if (isTrueIdent(w.ast, if_node.ast.cond_expr)) {
939 try w.transformations.ensureUnusedCapacity(1);
940 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
941 .to_replace = node_index,
942 .replacement = if_node.ast.then_expr,
943 } });
944 } else if (isFalseIdent(w.ast, if_node.ast.cond_expr)) {
945 try w.transformations.ensureUnusedCapacity(1);
946 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
947 .to_replace = node_index,
948 .replacement = if_node.ast.else_expr,
949 } });
950 }
951
885 try walkExpression(w, if_node.ast.cond_expr); // condition952 try walkExpression(w, if_node.ast.cond_expr); // condition
886953
887 if (if_node.ast.then_expr != 0) {954 if (if_node.ast.then_expr != 0) {
...@@ -1002,6 +1069,14 @@ fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {...@@ -1002,6 +1069,14 @@ fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1002 return isMatchingIdent(ast, node, "undefined");1069 return isMatchingIdent(ast, node, "undefined");
1003}1070}
10041071
1072fn isTrueIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1073 return isMatchingIdent(ast, node, "true");
1074}
1075
1076fn isFalseIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1077 return isMatchingIdent(ast, node, "false");
1078}
1079
1005fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {1080fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
1006 const node_tags = ast.nodes.items(.tag);1081 const node_tags = ast.nodes.items(.tag);
1007 const main_tokens = ast.nodes.items(.main_token);1082 const main_tokens = ast.nodes.items(.main_token);
...@@ -1014,3 +1089,14 @@ fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bo...@@ -1014,3 +1089,14 @@ fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bo
1014 else => return false,1089 else => return false,
1015 }1090 }
1016}1091}
1092
1093fn isEmptyBlock(ast: *const Ast, node: Ast.Node.Index) bool {
1094 const node_tags = ast.nodes.items(.tag);
1095 const node_data = ast.nodes.items(.data);
1096 switch (node_tags[node]) {
1097 .block_two => {
1098 return node_data[node].lhs == 0 and node_data[node].rhs == 0;
1099 },
1100 else => return false,
1101 }
1102}