authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-03 19:50:49-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-03 20:05:32-07:00
log13964796561a5e6ca16c76020bc3371858decb31
tree181663792c456e56d0a5975eee5ac1eff0a7355a
parentdff13e088b4da3fab50d72a8967b0d7cffeb9d7d

zig reduce: redesign

Now it works like this: 1. Walk the AST of the source file looking for independent reductions and collecting them all into an array list. 2. Randomize the list of transformations. A future enhancement will add priority weights to the sorting but for now they are completely shuffled. 3. Apply a subset consisting of 1/2 of the transformations and check for interestingness. 4. If not interesting, half the subset size again and check again. 5. Repeat until the subset size is 1, then march the transformation index forward by 1 with each non-interesting attempt. At any point if a subset of transformations succeeds in producing an interesting result, restart the whole process, reparsing the AST and re-generating the list of all possible transformations and shuffling it again. As for std.zig.render, the fixups operate based on AST Node Index rather than Nth index of the function occurence. This allows precise control over how to mutate the input.

3 files changed, 951 insertions(+), 64 deletions(-)

lib/std/zig/render.zig+13-14
...@@ -18,18 +18,24 @@ pub const Fixups = struct {...@@ -18,18 +18,24 @@ pub const Fixups = struct {
18 /// The key is the mut token (`var`/`const`) of the variable declaration18 /// The key is the mut token (`var`/`const`) of the variable declaration
19 /// that should have a `_ = foo;` inserted afterwards.19 /// that should have a `_ = foo;` inserted afterwards.
20 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .{},20 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .{},
21 /// The functions in this unordered set of indices will render with a21 /// The functions in this unordered set of AST fn decl nodes will render
22 /// function body of `@trap()` instead, with all parameters discarded.22 /// with a function body of `@trap()` instead, with all parameters
23 /// The indexes correspond to the order in which the functions appear in23 /// discarded.
24 /// the file.24 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
25 gut_functions: std.AutoHashMapUnmanaged(u32, void) = .{},
2625
27 pub fn count(f: Fixups) usize {26 pub fn count(f: Fixups) usize {
28 return f.unused_var_decls.count();27 return f.unused_var_decls.count() +
28 f.gut_functions.count();
29 }
30
31 pub fn clearRetainingCapacity(f: *Fixups) void {
32 f.unused_var_decls.clearRetainingCapacity();
33 f.gut_functions.clearRetainingCapacity();
29 }34 }
3035
31 pub fn deinit(f: *Fixups, gpa: Allocator) void {36 pub fn deinit(f: *Fixups, gpa: Allocator) void {
32 f.unused_var_decls.deinit(gpa);37 f.unused_var_decls.deinit(gpa);
38 f.gut_functions.deinit(gpa);
33 f.* = undefined;39 f.* = undefined;
34 }40 }
35};41};
...@@ -39,9 +45,6 @@ const Render = struct {...@@ -39,9 +45,6 @@ const Render = struct {
39 ais: *Ais,45 ais: *Ais,
40 tree: Ast,46 tree: Ast,
41 fixups: Fixups,47 fixups: Fixups,
42 /// Keeps track of how many function declarations we have seen so far. Used
43 /// by Fixups.
44 function_index: u32,
45};48};
4649
47pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void {50pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void {
...@@ -55,7 +58,6 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!v...@@ -55,7 +58,6 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!v
55 .ais = &auto_indenting_stream,58 .ais = &auto_indenting_stream,
56 .tree = tree,59 .tree = tree,
57 .fixups = fixups,60 .fixups = fixups,
58 .function_index = 0,
59 };61 };
6062
61 // Render all the line comments at the beginning of the file.63 // Render all the line comments at the beginning of the file.
...@@ -115,8 +117,6 @@ fn renderMember(...@@ -115,8 +117,6 @@ fn renderMember(
115 try renderDocComments(r, tree.firstToken(decl));117 try renderDocComments(r, tree.firstToken(decl));
116 switch (tree.nodes.items(.tag)[decl]) {118 switch (tree.nodes.items(.tag)[decl]) {
117 .fn_decl => {119 .fn_decl => {
118 const this_index = r.function_index;
119 r.function_index += 1;
120 // Some examples:120 // Some examples:
121 // pub extern "foo" fn ...121 // pub extern "foo" fn ...
122 // export fn ...122 // export fn ...
...@@ -162,7 +162,7 @@ fn renderMember(...@@ -162,7 +162,7 @@ fn renderMember(
162 assert(datas[decl].rhs != 0);162 assert(datas[decl].rhs != 0);
163 try renderExpression(r, fn_proto, .space);163 try renderExpression(r, fn_proto, .space);
164 const body_node = datas[decl].rhs;164 const body_node = datas[decl].rhs;
165 if (r.fixups.gut_functions.contains(this_index)) {165 if (r.fixups.gut_functions.contains(decl)) {
166 ais.pushIndent();166 ais.pushIndent();
167 const lbrace = tree.nodes.items(.main_token)[body_node];167 const lbrace = tree.nodes.items(.main_token)[body_node];
168 try renderToken(r, lbrace, .newline);168 try renderToken(r, lbrace, .newline);
...@@ -2136,7 +2136,6 @@ fn renderArrayInit(...@@ -2136,7 +2136,6 @@ fn renderArrayInit(
2136 .ais = &auto_indenting_stream,2136 .ais = &auto_indenting_stream,
2137 .tree = r.tree,2137 .tree = r.tree,
2138 .fixups = r.fixups,2138 .fixups = r.fixups,
2139 .function_index = r.function_index,
2140 };2139 };
21412140
2142 // Calculate size of columns in current section2141 // Calculate size of columns in current section
src/reduce.zig+146-50
...@@ -3,6 +3,8 @@ const mem = std.mem;...@@ -3,6 +3,8 @@ const mem = std.mem;
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;4const assert = std.debug.assert;
5const fatal = @import("./main.zig").fatal;5const fatal = @import("./main.zig").fatal;
6const Ast = std.zig.Ast;
7const Walk = @import("reduce/Walk.zig");
68
7const usage =9const usage =
8 \\zig reduce [options] ./checker root_source_file.zig [-- [argv]]10 \\zig reduce [options] ./checker root_source_file.zig [-- [argv]]
...@@ -16,6 +18,8 @@ const usage =...@@ -16,6 +18,8 @@ const usage =
16 \\ exit(other): not interesting18 \\ exit(other): not interesting
17 \\19 \\
18 \\options:20 \\options:
21 \\ --seed [integer] Override the random seed. Defaults to 0
22 \\ --skip-smoke-test Skip interestingness check smoke test
19 \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name23 \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name
20 \\ deps: [dep],[dep],...24 \\ deps: [dep],[dep],...
21 \\ dep: [[import=]name]25 \\ dep: [[import=]name]
...@@ -32,20 +36,23 @@ const Interestingness = enum { interesting, unknown, boring };...@@ -32,20 +36,23 @@ const Interestingness = enum { interesting, unknown, boring };
3236
33// Roadmap:37// Roadmap:
34// - add thread pool38// - add thread pool
35// - add support for `@import` detection and other files39// - add support for parsing the module flags
36// - more fancy transformations40// - more fancy transformations
37// - reduce flags sent to the compiler41// - @import inlining of modules
38// - @import inlining42// - @import inlining of files
39// - deleting unused functions and other globals43// - deleting unused functions and other globals
40// - removing statements or blocks of code44// - removing statements or blocks of code
41// - replacing operands of `and` and `or` with `true` and `false`45// - replacing operands of `and` and `or` with `true` and `false`
42// - replacing if conditions with `true` and `false`46// - replacing if conditions with `true` and `false`
43// - integrate the build system?47// - reduce flags sent to the compiler
48// - integrate with the build system?
4449
45pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {50pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
46 var opt_checker_path: ?[]const u8 = null;51 var opt_checker_path: ?[]const u8 = null;
47 var opt_root_source_file_path: ?[]const u8 = null;52 var opt_root_source_file_path: ?[]const u8 = null;
48 var argv: []const []const u8 = &.{};53 var argv: []const []const u8 = &.{};
54 var seed: u32 = 0;
55 var skip_smoke_test = false;
4956
50 {57 {
51 var i: usize = 2; // skip over "zig" and "reduce"58 var i: usize = 2; // skip over "zig" and "reduce"
...@@ -59,6 +66,23 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -59,6 +66,23 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
59 } else if (mem.eql(u8, arg, "--")) {66 } else if (mem.eql(u8, arg, "--")) {
60 argv = args[i + 1 ..];67 argv = args[i + 1 ..];
61 break;68 break;
69 } else if (mem.eql(u8, arg, "--skip-smoke-test")) {
70 skip_smoke_test = true;
71 } else if (mem.eql(u8, arg, "--main-mod-path")) {
72 @panic("TODO: implement --main-mod-path");
73 } else if (mem.eql(u8, arg, "--mod")) {
74 @panic("TODO: implement --mod");
75 } else if (mem.eql(u8, arg, "--deps")) {
76 @panic("TODO: implement --deps");
77 } else if (mem.eql(u8, arg, "--seed")) {
78 i += 1;
79 if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});
80 const next_arg = args[i];
81 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
82 fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{
83 next_arg, @errorName(err),
84 });
85 };
62 } else {86 } else {
63 fatal("unrecognized parameter: '{s}'", .{arg});87 fatal("unrecognized parameter: '{s}'", .{arg});
64 }88 }
...@@ -85,30 +109,11 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -85,30 +109,11 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
85 var rendered = std.ArrayList(u8).init(gpa);109 var rendered = std.ArrayList(u8).init(gpa);
86 defer rendered.deinit();110 defer rendered.deinit();
87111
88 var prev_rendered = std.ArrayList(u8).init(gpa);112 var tree = try parse(gpa, arena, root_source_file_path);
89 defer prev_rendered.deinit();
90
91 const source_code = try std.fs.cwd().readFileAllocOptions(
92 arena,
93 root_source_file_path,
94 std.math.maxInt(u32),
95 null,
96 1,
97 0,
98 );
99
100 var tree = try std.zig.Ast.parse(gpa, source_code, .zig);
101 defer tree.deinit(gpa);113 defer tree.deinit(gpa);
102114
103 if (tree.errors.len != 0) {115 if (!skip_smoke_test) {
104 @panic("syntax errors occurred");116 std.debug.print("smoke testing the interestingness check...\n", .{});
105 }
106
107 var next_gut_fn_index: u32 = 0;
108 var fixups: std.zig.Ast.Fixups = .{};
109
110 {
111 // smoke test the interestingness check
112 switch (try runCheck(arena, interestingness_argv.items)) {117 switch (try runCheck(arena, interestingness_argv.items)) {
113 .interesting => {},118 .interesting => {},
114 .boring, .unknown => |t| {119 .boring, .unknown => |t| {
...@@ -119,40 +124,97 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -119,40 +124,97 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
119 }124 }
120 }125 }
121126
122 while (true) {127 var fixups: Ast.Fixups = .{};
123 try fixups.gut_functions.put(arena, next_gut_fn_index, {});128 defer fixups.deinit(gpa);
129 var rng = std.rand.DefaultPrng.init(seed);
124130
125 rendered.clearRetainingCapacity();131 // 1. Walk the AST of the source file looking for independent
126 try tree.renderToArrayList(&rendered, fixups);132 // reductions and collecting them all into an array list.
133 // 2. Randomize the list of transformations. A future enhancement will add
134 // priority weights to the sorting but for now they are completely
135 // shuffled.
136 // 3. Apply a subset consisting of 1/2 of the transformations and check for
137 // interestingness.
138 // 4. If not interesting, half the subset size again and check again.
139 // 5. Repeat until the subset size is 1, then march the transformation
140 // index forward by 1 with each non-interesting attempt.
141 //
142 // At any point if a subset of transformations succeeds in producing an interesting
143 // result, restart the whole process, reparsing the AST and re-generating the list
144 // of all possible transformations and shuffling it again.
127145
128 if (mem.eql(u8, rendered.items, prev_rendered.items)) {146 var transformations = std.ArrayList(Walk.Transformation).init(gpa);
129 std.debug.print("no remaining transformations\n", .{});147 defer transformations.deinit();
130 break;148 try Walk.findTransformations(&tree, &transformations);
131 }149 sortTransformations(transformations.items, rng.random());
132 prev_rendered.clearRetainingCapacity();
133 try prev_rendered.appendSlice(rendered.items);
134150
135 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);151 fresh: while (transformations.items.len > 0) {
152 std.debug.print("found {d} possible transformations\n", .{
153 transformations.items.len,
154 });
155 var subset_size: usize = transformations.items.len;
156 var start_index: usize = 0;
136157
137 const interestingness = try runCheck(arena, interestingness_argv.items);158 while (start_index < transformations.items.len) {
138 std.debug.print("{s}\n", .{@tagName(interestingness)});159 subset_size = @max(1, subset_size / 2);
139 switch (interestingness) {
140 .interesting => {
141 next_gut_fn_index += 1;
142 },
143 .unknown, .boring => {
144 // revert the change and try the next transformation
145 assert(fixups.gut_functions.remove(next_gut_fn_index));
146 next_gut_fn_index += 1;
147160
148 rendered.clearRetainingCapacity();161 const this_set = transformations.items[start_index..][0..subset_size];
149 try tree.renderToArrayList(&rendered, fixups);162 try transformationsToFixups(gpa, this_set, &fixups);
150 },163
164 rendered.clearRetainingCapacity();
165 try tree.renderToArrayList(&rendered, fixups);
166 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
167
168 const interestingness = try runCheck(arena, interestingness_argv.items);
169 std.debug.print("{d} random transformations: {s}\n", .{
170 subset_size, @tagName(interestingness),
171 });
172 switch (interestingness) {
173 .interesting => {
174 const new_tree = try parse(gpa, arena, root_source_file_path);
175 tree.deinit(gpa);
176 tree = new_tree;
177
178 try Walk.findTransformations(&tree, &transformations);
179 // Resetting based on the seed again means we will get the same
180 // results if restarting the reduction process from this new point.
181 rng = std.rand.DefaultPrng.init(seed);
182 sortTransformations(transformations.items, rng.random());
183
184 continue :fresh;
185 },
186 .unknown, .boring => {
187 // Continue to try the next set of transformations.
188 // If we tested only one transformation, move on to the next one.
189 if (subset_size == 1) {
190 start_index += 1;
191 }
192 },
193 }
151 }194 }
195 std.debug.print("all {d} remaining transformations are uninteresting\n", .{
196 transformations.items.len,
197 });
198
199 // Revert the source back to not be transformed.
200 fixups.clearRetainingCapacity();
201 rendered.clearRetainingCapacity();
202 try tree.renderToArrayList(&rendered, fixups);
203 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
204
205 return std.process.cleanExit();
152 }206 }
207 std.debug.print("no more transformations found\n", .{});
153 return std.process.cleanExit();208 return std.process.cleanExit();
154}209}
155210
211fn sortTransformations(transformations: []Walk.Transformation, rng: std.rand.Random) void {
212 rng.shuffle(Walk.Transformation, transformations);
213 // Stable sort based on priority to keep randomness as the secondary sort.
214 // TODO: introduce transformation priorities
215 // std.mem.sort(transformations);
216}
217
156fn termToInteresting(term: std.process.Child.Term) Interestingness {218fn termToInteresting(term: std.process.Child.Term) Interestingness {
157 return switch (term) {219 return switch (term) {
158 .Exited => |code| switch (code) {220 .Exited => |code| switch (code) {
...@@ -176,3 +238,37 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness...@@ -176,3 +238,37 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness
176 std.debug.print("{s}", .{result.stderr});238 std.debug.print("{s}", .{result.stderr});
177 return termToInteresting(result.term);239 return termToInteresting(result.term);
178}240}
241
242fn transformationsToFixups(
243 gpa: Allocator,
244 transforms: []const Walk.Transformation,
245 fixups: *Ast.Fixups,
246) !void {
247 fixups.clearRetainingCapacity();
248
249 for (transforms) |t| switch (t) {
250 .gut_function => |fn_decl_node| {
251 try fixups.gut_functions.put(gpa, fn_decl_node, {});
252 },
253 };
254}
255
256fn parse(gpa: Allocator, arena: Allocator, root_source_file_path: []const u8) !Ast {
257 const source_code = try std.fs.cwd().readFileAllocOptions(
258 arena,
259 root_source_file_path,
260 std.math.maxInt(u32),
261 null,
262 1,
263 0,
264 );
265
266 var tree = try Ast.parse(gpa, source_code, .zig);
267 errdefer tree.deinit(gpa);
268
269 if (tree.errors.len != 0) {
270 @panic("syntax errors occurred");
271 }
272
273 return tree;
274}
src/reduce/Walk.zig created+792
...@@ -0,0 +1,792 @@
1const std = @import("std");
2const Ast = std.zig.Ast;
3const Walk = @This();
4const assert = std.debug.assert;
5
6ast: *const Ast,
7transformations: *std.ArrayList(Transformation),
8
9pub const Transformation = union(enum) {
10 /// Replace the fn decl AST Node with one whose body is only `@trap()` with
11 /// discarded parameters.
12 gut_function: Ast.Node.Index,
13};
14
15pub const Error = error{OutOfMemory};
16
17/// The result will be priority shuffled.
18pub fn findTransformations(ast: *const Ast, transformations: *std.ArrayList(Transformation)) !void {
19 transformations.clearRetainingCapacity();
20
21 var walk: Walk = .{
22 .ast = ast,
23 .transformations = transformations,
24 };
25 try walkMembers(&walk, walk.ast.rootDecls());
26}
27
28fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void {
29 for (members) |member| {
30 try walkMember(w, member);
31 }
32}
33
34fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
35 const ast = w.ast;
36 const datas = ast.nodes.items(.data);
37 switch (ast.nodes.items(.tag)[decl]) {
38 .fn_decl => {
39 const fn_proto = datas[decl].lhs;
40 try walkExpression(w, fn_proto);
41 const body_node = datas[decl].rhs;
42 if (!isFnBodyGutted(ast, body_node)) {
43 try w.transformations.append(.{ .gut_function = decl });
44 }
45 try walkExpression(w, body_node);
46 },
47 .fn_proto_simple,
48 .fn_proto_multi,
49 .fn_proto_one,
50 .fn_proto,
51 => {
52 try walkExpression(w, decl);
53 },
54
55 .@"usingnamespace" => {
56 const expr = datas[decl].lhs;
57 try walkExpression(w, expr);
58 },
59
60 .global_var_decl,
61 .local_var_decl,
62 .simple_var_decl,
63 .aligned_var_decl,
64 => try walkVarDecl(w, ast.fullVarDecl(decl).?),
65
66 .test_decl => {
67 try walkExpression(w, datas[decl].rhs);
68 },
69
70 .container_field_init,
71 .container_field_align,
72 .container_field,
73 => try walkContainerField(w, ast.fullContainerField(decl).?),
74
75 .@"comptime" => try walkExpression(w, decl),
76
77 .root => unreachable,
78 else => unreachable,
79 }
80}
81
82fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
83 const ast = w.ast;
84 const token_tags = ast.tokens.items(.tag);
85 const main_tokens = ast.nodes.items(.main_token);
86 const node_tags = ast.nodes.items(.tag);
87 const datas = ast.nodes.items(.data);
88 switch (node_tags[node]) {
89 .identifier => {},
90
91 .number_literal,
92 .char_literal,
93 .unreachable_literal,
94 .anyframe_literal,
95 .string_literal,
96 => {},
97
98 .multiline_string_literal => {},
99
100 .error_value => {},
101
102 .block_two,
103 .block_two_semicolon,
104 => {
105 const statements = [2]Ast.Node.Index{ datas[node].lhs, datas[node].rhs };
106 if (datas[node].lhs == 0) {
107 return walkBlock(w, node, statements[0..0]);
108 } else if (datas[node].rhs == 0) {
109 return walkBlock(w, node, statements[0..1]);
110 } else {
111 return walkBlock(w, node, statements[0..2]);
112 }
113 },
114 .block,
115 .block_semicolon,
116 => {
117 const statements = ast.extra_data[datas[node].lhs..datas[node].rhs];
118 return walkBlock(w, node, statements);
119 },
120
121 .@"errdefer" => {
122 const expr = datas[node].rhs;
123 return walkExpression(w, expr);
124 },
125
126 .@"defer" => {
127 const expr = datas[node].rhs;
128 return walkExpression(w, expr);
129 },
130 .@"comptime", .@"nosuspend" => {
131 const block = datas[node].lhs;
132 return walkExpression(w, block);
133 },
134
135 .@"suspend" => {
136 const body = datas[node].lhs;
137 return walkExpression(w, body);
138 },
139
140 .@"catch" => {
141 try walkExpression(w, datas[node].lhs); // target
142 try walkExpression(w, datas[node].rhs); // fallback
143 },
144
145 .field_access => {
146 const field_access = datas[node];
147 try walkExpression(w, field_access.lhs);
148 },
149
150 .error_union,
151 .switch_range,
152 => {
153 const infix = datas[node];
154 try walkExpression(w, infix.lhs);
155 return walkExpression(w, infix.rhs);
156 },
157 .for_range => {
158 const infix = datas[node];
159 try walkExpression(w, infix.lhs);
160 if (infix.rhs != 0) {
161 return walkExpression(w, infix.rhs);
162 }
163 },
164
165 .add,
166 .add_wrap,
167 .add_sat,
168 .array_cat,
169 .array_mult,
170 .assign,
171 .assign_bit_and,
172 .assign_bit_or,
173 .assign_shl,
174 .assign_shl_sat,
175 .assign_shr,
176 .assign_bit_xor,
177 .assign_div,
178 .assign_sub,
179 .assign_sub_wrap,
180 .assign_sub_sat,
181 .assign_mod,
182 .assign_add,
183 .assign_add_wrap,
184 .assign_add_sat,
185 .assign_mul,
186 .assign_mul_wrap,
187 .assign_mul_sat,
188 .bang_equal,
189 .bit_and,
190 .bit_or,
191 .shl,
192 .shl_sat,
193 .shr,
194 .bit_xor,
195 .bool_and,
196 .bool_or,
197 .div,
198 .equal_equal,
199 .greater_or_equal,
200 .greater_than,
201 .less_or_equal,
202 .less_than,
203 .merge_error_sets,
204 .mod,
205 .mul,
206 .mul_wrap,
207 .mul_sat,
208 .sub,
209 .sub_wrap,
210 .sub_sat,
211 .@"orelse",
212 => {
213 const infix = datas[node];
214 try walkExpression(w, infix.lhs);
215 try walkExpression(w, infix.rhs);
216 },
217
218 .assign_destructure => {
219 const lhs_count = ast.extra_data[datas[node].lhs];
220 assert(lhs_count > 1);
221 const lhs_exprs = ast.extra_data[datas[node].lhs + 1 ..][0..lhs_count];
222 const rhs = datas[node].rhs;
223
224 for (lhs_exprs) |lhs_node| {
225 switch (node_tags[lhs_node]) {
226 .global_var_decl,
227 .local_var_decl,
228 .simple_var_decl,
229 .aligned_var_decl,
230 => try walkVarDecl(w, ast.fullVarDecl(lhs_node).?),
231 else => try walkExpression(w, lhs_node),
232 }
233 }
234 return walkExpression(w, rhs);
235 },
236
237 .bit_not,
238 .bool_not,
239 .negation,
240 .negation_wrap,
241 .optional_type,
242 .address_of,
243 => {
244 return walkExpression(w, datas[node].lhs);
245 },
246
247 .@"try",
248 .@"resume",
249 .@"await",
250 => {
251 return walkExpression(w, datas[node].lhs);
252 },
253
254 .array_type,
255 .array_type_sentinel,
256 => {},
257
258 .ptr_type_aligned,
259 .ptr_type_sentinel,
260 .ptr_type,
261 .ptr_type_bit_range,
262 => {},
263
264 .array_init_one,
265 .array_init_one_comma,
266 .array_init_dot_two,
267 .array_init_dot_two_comma,
268 .array_init_dot,
269 .array_init_dot_comma,
270 .array_init,
271 .array_init_comma,
272 => {
273 var elements: [2]Ast.Node.Index = undefined;
274 return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
275 },
276
277 .struct_init_one,
278 .struct_init_one_comma,
279 .struct_init_dot_two,
280 .struct_init_dot_two_comma,
281 .struct_init_dot,
282 .struct_init_dot_comma,
283 .struct_init,
284 .struct_init_comma,
285 => {
286 var buf: [2]Ast.Node.Index = undefined;
287 return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
288 },
289
290 .call_one,
291 .call_one_comma,
292 .async_call_one,
293 .async_call_one_comma,
294 .call,
295 .call_comma,
296 .async_call,
297 .async_call_comma,
298 => {
299 var buf: [1]Ast.Node.Index = undefined;
300 return walkCall(w, ast.fullCall(&buf, node).?);
301 },
302
303 .array_access => {
304 const suffix = datas[node];
305 try walkExpression(w, suffix.lhs);
306 try walkExpression(w, suffix.rhs);
307 },
308
309 .slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
310
311 .deref => {
312 try walkExpression(w, datas[node].lhs);
313 },
314
315 .unwrap_optional => {
316 try walkExpression(w, datas[node].lhs);
317 },
318
319 .@"break" => {
320 const label_token = datas[node].lhs;
321 const target = datas[node].rhs;
322 if (label_token == 0 and target == 0) {
323 // no expressions
324 } else if (label_token == 0 and target != 0) {
325 try walkExpression(w, target);
326 } else if (label_token != 0 and target == 0) {
327 try walkIdentifier(w, label_token);
328 } else if (label_token != 0 and target != 0) {
329 try walkExpression(w, target);
330 }
331 },
332
333 .@"continue" => {
334 const label = datas[node].lhs;
335 if (label != 0) {
336 return walkIdentifier(w, label); // label
337 }
338 },
339
340 .@"return" => {
341 if (datas[node].lhs != 0) {
342 try walkExpression(w, datas[node].lhs);
343 }
344 },
345
346 .grouped_expression => {
347 try walkExpression(w, datas[node].lhs);
348 },
349
350 .container_decl,
351 .container_decl_trailing,
352 .container_decl_arg,
353 .container_decl_arg_trailing,
354 .container_decl_two,
355 .container_decl_two_trailing,
356 .tagged_union,
357 .tagged_union_trailing,
358 .tagged_union_enum_tag,
359 .tagged_union_enum_tag_trailing,
360 .tagged_union_two,
361 .tagged_union_two_trailing,
362 => {
363 var buf: [2]Ast.Node.Index = undefined;
364 return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
365 },
366
367 .error_set_decl => {
368 const error_token = main_tokens[node];
369 const lbrace = error_token + 1;
370 const rbrace = datas[node].rhs;
371
372 var i = lbrace + 1;
373 while (i < rbrace) : (i += 1) {
374 switch (token_tags[i]) {
375 .doc_comment => unreachable, // TODO
376 .identifier => try walkIdentifier(w, i),
377 .comma => {},
378 else => unreachable,
379 }
380 }
381 },
382
383 .builtin_call_two, .builtin_call_two_comma => {
384 if (datas[node].lhs == 0) {
385 return walkBuiltinCall(w, main_tokens[node], &.{});
386 } else if (datas[node].rhs == 0) {
387 return walkBuiltinCall(w, main_tokens[node], &.{datas[node].lhs});
388 } else {
389 return walkBuiltinCall(w, main_tokens[node], &.{ datas[node].lhs, datas[node].rhs });
390 }
391 },
392 .builtin_call, .builtin_call_comma => {
393 const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
394 return walkBuiltinCall(w, main_tokens[node], params);
395 },
396
397 .fn_proto_simple,
398 .fn_proto_multi,
399 .fn_proto_one,
400 .fn_proto,
401 => {
402 var buf: [1]Ast.Node.Index = undefined;
403 return walkFnProto(w, ast.fullFnProto(&buf, node).?);
404 },
405
406 .anyframe_type => {
407 if (datas[node].rhs != 0) {
408 return walkExpression(w, datas[node].rhs);
409 }
410 },
411
412 .@"switch",
413 .switch_comma,
414 => {
415 const condition = datas[node].lhs;
416 const extra = ast.extraData(datas[node].rhs, Ast.Node.SubRange);
417 const cases = ast.extra_data[extra.start..extra.end];
418
419 try walkExpression(w, condition); // condition expression
420 try walkExpressions(w, cases);
421 },
422
423 .switch_case_one,
424 .switch_case_inline_one,
425 .switch_case,
426 .switch_case_inline,
427 => return walkSwitchCase(w, ast.fullSwitchCase(node).?),
428
429 .while_simple,
430 .while_cont,
431 .@"while",
432 => return walkWhile(w, ast.fullWhile(node).?),
433
434 .for_simple,
435 .@"for",
436 => return walkFor(w, ast.fullFor(node).?),
437
438 .if_simple,
439 .@"if",
440 => return walkIf(w, ast.fullIf(node).?),
441
442 .asm_simple,
443 .@"asm",
444 => return walkAsm(w, ast.fullAsm(node).?),
445
446 .enum_literal => {
447 return walkIdentifier(w, main_tokens[node]); // name
448 },
449
450 .fn_decl => unreachable,
451 .container_field => unreachable,
452 .container_field_init => unreachable,
453 .container_field_align => unreachable,
454 .root => unreachable,
455 .global_var_decl => unreachable,
456 .local_var_decl => unreachable,
457 .simple_var_decl => unreachable,
458 .aligned_var_decl => unreachable,
459 .@"usingnamespace" => unreachable,
460 .test_decl => unreachable,
461 .asm_output => unreachable,
462 .asm_input => unreachable,
463 }
464}
465
466fn walkVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
467 try walkIdentifier(w, var_decl.ast.mut_token + 1); // name
468
469 if (var_decl.ast.type_node != 0) {
470 try walkExpression(w, var_decl.ast.type_node);
471 }
472
473 if (var_decl.ast.align_node != 0) {
474 try walkExpression(w, var_decl.ast.align_node);
475 }
476
477 if (var_decl.ast.addrspace_node != 0) {
478 try walkExpression(w, var_decl.ast.addrspace_node);
479 }
480
481 if (var_decl.ast.section_node != 0) {
482 try walkExpression(w, var_decl.ast.section_node);
483 }
484
485 assert(var_decl.ast.init_node != 0);
486
487 return walkExpression(w, var_decl.ast.init_node);
488}
489
490fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
491 if (field.ast.type_expr != 0) {
492 try walkExpression(w, field.ast.type_expr); // type
493 }
494 if (field.ast.align_expr != 0) {
495 try walkExpression(w, field.ast.align_expr); // alignment
496 }
497 try walkExpression(w, field.ast.value_expr); // value
498}
499
500fn walkBlock(
501 w: *Walk,
502 block_node: Ast.Node.Index,
503 statements: []const Ast.Node.Index,
504) Error!void {
505 _ = block_node;
506 const ast = w.ast;
507 const node_tags = ast.nodes.items(.tag);
508
509 for (statements) |stmt| {
510 switch (node_tags[stmt]) {
511 .global_var_decl,
512 .local_var_decl,
513 .simple_var_decl,
514 .aligned_var_decl,
515 => try walkVarDecl(w, ast.fullVarDecl(stmt).?),
516
517 else => try walkExpression(w, stmt),
518 }
519 }
520}
521
522fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
523 try walkExpression(w, array_type.ast.elem_count);
524 if (array_type.ast.sentinel != 0) {
525 try walkExpression(w, array_type.ast.sentinel);
526 }
527 return walkExpression(w, array_type.ast.elem_type);
528}
529
530fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
531 if (array_init.ast.type_expr != 0) {
532 try walkExpression(w, array_init.ast.type_expr); // T
533 }
534 for (array_init.ast.elements) |elem_init| {
535 try walkExpression(w, elem_init);
536 }
537}
538
539fn walkStructInit(
540 w: *Walk,
541 struct_node: Ast.Node.Index,
542 struct_init: Ast.full.StructInit,
543) Error!void {
544 _ = struct_node;
545 if (struct_init.ast.type_expr != 0) {
546 try walkExpression(w, struct_init.ast.type_expr); // T
547 }
548 for (struct_init.ast.fields) |field_init| {
549 try walkExpression(w, field_init);
550 }
551}
552
553fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
554 try walkExpression(w, call.ast.fn_expr);
555 try walkParamList(w, call.ast.params);
556}
557
558fn walkSlice(
559 w: *Walk,
560 slice_node: Ast.Node.Index,
561 slice: Ast.full.Slice,
562) Error!void {
563 _ = slice_node;
564 try walkExpression(w, slice.ast.sliced);
565 try walkExpression(w, slice.ast.start);
566 if (slice.ast.end != 0) {
567 try walkExpression(w, slice.ast.end);
568 }
569 if (slice.ast.sentinel != 0) {
570 try walkExpression(w, slice.ast.sentinel);
571 }
572}
573
574fn walkIdentifier(w: *Walk, token_index: Ast.TokenIndex) Error!void {
575 _ = w;
576 _ = token_index;
577}
578
579fn walkContainerDecl(
580 w: *Walk,
581 container_decl_node: Ast.Node.Index,
582 container_decl: Ast.full.ContainerDecl,
583) Error!void {
584 _ = container_decl_node;
585 if (container_decl.ast.arg != 0) {
586 try walkExpression(w, container_decl.ast.arg);
587 }
588 for (container_decl.ast.members) |member| {
589 try walkMember(w, member);
590 }
591}
592
593fn walkBuiltinCall(
594 w: *Walk,
595 builtin_token: Ast.TokenIndex,
596 params: []const Ast.Node.Index,
597) Error!void {
598 _ = builtin_token;
599 for (params) |param_node| {
600 try walkExpression(w, param_node);
601 }
602}
603
604fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
605 const ast = w.ast;
606
607 {
608 var it = fn_proto.iterate(ast);
609 while (it.next()) |param| {
610 if (param.type_expr != 0) {
611 try walkExpression(w, param.type_expr);
612 }
613 }
614 }
615
616 if (fn_proto.ast.align_expr != 0) {
617 try walkExpression(w, fn_proto.ast.align_expr);
618 }
619
620 if (fn_proto.ast.addrspace_expr != 0) {
621 try walkExpression(w, fn_proto.ast.addrspace_expr);
622 }
623
624 if (fn_proto.ast.section_expr != 0) {
625 try walkExpression(w, fn_proto.ast.section_expr);
626 }
627
628 if (fn_proto.ast.callconv_expr != 0) {
629 try walkExpression(w, fn_proto.ast.callconv_expr);
630 }
631
632 try walkExpression(w, fn_proto.ast.return_type);
633}
634
635fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
636 for (expressions) |expression| {
637 try walkExpression(w, expression);
638 }
639}
640
641fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
642 for (switch_case.ast.values) |value_expr| {
643 try walkExpression(w, value_expr);
644 }
645 try walkExpression(w, switch_case.ast.target_expr);
646}
647
648fn walkWhile(w: *Walk, while_node: Ast.full.While) Error!void {
649 try walkExpression(w, while_node.ast.cond_expr); // condition
650
651 if (while_node.ast.cont_expr != 0) {
652 try walkExpression(w, while_node.ast.cont_expr);
653 }
654
655 try walkExpression(w, while_node.ast.cond_expr); // condition
656
657 if (while_node.ast.then_expr != 0) {
658 try walkExpression(w, while_node.ast.then_expr);
659 }
660 if (while_node.ast.else_expr != 0) {
661 try walkExpression(w, while_node.ast.else_expr);
662 }
663}
664
665fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
666 try walkParamList(w, for_node.ast.inputs);
667 if (for_node.ast.then_expr != 0) {
668 try walkExpression(w, for_node.ast.then_expr);
669 }
670 if (for_node.ast.else_expr != 0) {
671 try walkExpression(w, for_node.ast.else_expr);
672 }
673}
674
675fn walkIf(w: *Walk, if_node: Ast.full.If) Error!void {
676 try walkExpression(w, if_node.ast.cond_expr); // condition
677
678 if (if_node.ast.then_expr != 0) {
679 try walkExpression(w, if_node.ast.then_expr);
680 }
681 if (if_node.ast.else_expr != 0) {
682 try walkExpression(w, if_node.ast.else_expr);
683 }
684}
685
686fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
687 try walkExpression(w, asm_node.ast.template);
688 for (asm_node.ast.items) |item| {
689 try walkExpression(w, item);
690 }
691}
692
693fn walkParamList(w: *Walk, params: []const Ast.Node.Index) Error!void {
694 for (params) |param_node| {
695 try walkExpression(w, param_node);
696 }
697}
698
699/// Check if it is already gutted (i.e. its body replaced with `@trap()`).
700fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
701 // skip over discards
702 const node_tags = ast.nodes.items(.tag);
703 const datas = ast.nodes.items(.data);
704 var statements_buf: [2]Ast.Node.Index = undefined;
705 const statements = switch (node_tags[body_node]) {
706 .block_two,
707 .block_two_semicolon,
708 => blk: {
709 statements_buf[0..2].* = .{ datas[body_node].lhs, datas[body_node].rhs };
710 break :blk if (datas[body_node].lhs == 0)
711 statements_buf[0..0]
712 else if (datas[body_node].rhs == 0)
713 statements_buf[0..1]
714 else
715 statements_buf[0..2];
716 },
717
718 .block,
719 .block_semicolon,
720 => ast.extra_data[datas[body_node].lhs..datas[body_node].rhs],
721
722 else => return false,
723 };
724 var i: usize = 0;
725 while (i < statements.len) : (i += 1) {
726 switch (categorizeStmt(ast, statements[i])) {
727 .discard_identifier => continue,
728 .trap_call => return i + 1 == statements.len,
729 else => return false,
730 }
731 }
732 return false;
733}
734
735const StmtCategory = enum {
736 discard_identifier,
737 trap_call,
738 other,
739};
740
741fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
742 const node_tags = ast.nodes.items(.tag);
743 const datas = ast.nodes.items(.data);
744 const main_tokens = ast.nodes.items(.main_token);
745 switch (node_tags[stmt]) {
746 .builtin_call_two, .builtin_call_two_comma => {
747 if (datas[stmt].lhs == 0) {
748 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{});
749 } else if (datas[stmt].rhs == 0) {
750 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{datas[stmt].lhs});
751 } else {
752 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{ datas[stmt].lhs, datas[stmt].rhs });
753 }
754 },
755 .builtin_call, .builtin_call_comma => {
756 const params = ast.extra_data[datas[stmt].lhs..datas[stmt].rhs];
757 return categorizeBuiltinCall(ast, main_tokens[stmt], params);
758 },
759 .assign => {
760 const infix = datas[stmt];
761 if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier)
762 return .discard_identifier;
763 return .other;
764 },
765 else => return .other,
766 }
767}
768
769fn categorizeBuiltinCall(
770 ast: *const Ast,
771 builtin_token: Ast.TokenIndex,
772 params: []const Ast.Node.Index,
773) StmtCategory {
774 if (params.len != 0) return .other;
775 const name_bytes = ast.tokenSlice(builtin_token);
776 if (std.mem.eql(u8, name_bytes, "@trap"))
777 return .trap_call;
778 return .other;
779}
780
781fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
782 const node_tags = ast.nodes.items(.tag);
783 const main_tokens = ast.nodes.items(.main_token);
784 switch (node_tags[node]) {
785 .identifier => {
786 const token_index = main_tokens[node];
787 const name_bytes = ast.tokenSlice(token_index);
788 return std.mem.eql(u8, name_bytes, "_");
789 },
790 else => return false,
791 }
792}