authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-26 23:07:11-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-26 23:43:42-07:00
log0157e1196c77702f07d44c63c71246ff5e5616f1
tree126ba127edc0d29409e014ae453600fab350e893
parentba575595bb61d95e5304ea6f1ecd125c18a66617

compiler: JIT zig reduce

See #19063

7 files changed, 1542 insertions(+), 1536 deletions(-)

build.zig-4
......@@ -34,7 +34,6 @@ pub fn build(b: *std.Build) !void {
3434 const skip_install_langref = b.option(bool, "no-langref", "skip copying of langref to the installation prefix") orelse skip_install_lib_files;
3535 const skip_install_autodocs = b.option(bool, "no-autodocs", "skip copying of standard library autodocs to the installation prefix") orelse skip_install_lib_files;
3636 const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false;
37 const only_reduce = b.option(bool, "only-reduce", "only build zig reduce") orelse false;
3837
3938 const docgen_exe = b.addExecutable(.{
4039 .name = "docgen",
......@@ -245,7 +244,6 @@ pub fn build(b: *std.Build) !void {
245244 exe_options.addOption(bool, "force_gpa", force_gpa);
246245 exe_options.addOption(bool, "only_c", only_c);
247246 exe_options.addOption(bool, "only_core_functionality", only_c);
248 exe_options.addOption(bool, "only_reduce", only_reduce);
249247
250248 if (link_libc) {
251249 exe.linkLibC();
......@@ -407,7 +405,6 @@ pub fn build(b: *std.Build) !void {
407405 test_cases_options.addOption(bool, "force_gpa", force_gpa);
408406 test_cases_options.addOption(bool, "only_c", only_c);
409407 test_cases_options.addOption(bool, "only_core_functionality", true);
410 test_cases_options.addOption(bool, "only_reduce", false);
411408 test_cases_options.addOption(bool, "enable_qemu", b.enable_qemu);
412409 test_cases_options.addOption(bool, "enable_wine", b.enable_wine);
413410 test_cases_options.addOption(bool, "enable_wasmtime", b.enable_wasmtime);
......@@ -599,7 +596,6 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
599596 exe_options.addOption(bool, "enable_tracy_allocation", false);
600597 exe_options.addOption(bool, "value_tracing", false);
601598 exe_options.addOption(bool, "only_core_functionality", true);
602 exe_options.addOption(bool, "only_reduce", false);
603599
604600 const run_opt = b.addSystemCommand(&.{
605601 "wasm-opt",
lib/std/zig/reduce.zig created+426
......@@ -0,0 +1,426 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5const Ast = std.zig.Ast;
6const Walk = @import("reduce/Walk.zig");
7const AstGen = std.zig.AstGen;
8const Zir = std.zig.Zir;
9
10const usage =
11 \\zig reduce [options] ./checker root_source_file.zig [-- [argv]]
12 \\
13 \\root_source_file.zig is relative to --main-mod-path.
14 \\
15 \\checker:
16 \\ An executable that communicates interestingness by returning these exit codes:
17 \\ exit(0): interesting
18 \\ exit(1): unknown (infinite loop or other mishap)
19 \\ exit(other): not interesting
20 \\
21 \\options:
22 \\ --seed [integer] Override the random seed. Defaults to 0
23 \\ --skip-smoke-test Skip interestingness check smoke test
24 \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name
25 \\ deps: [dep],[dep],...
26 \\ dep: [[import=]name]
27 \\ --deps [dep],[dep],... Set dependency names for the root package
28 \\ dep: [[import=]name]
29 \\ --main-mod-path Set the directory of the root module
30 \\
31 \\argv:
32 \\ Forwarded directly to the interestingness script.
33 \\
34;
35
36const Interestingness = enum { interesting, unknown, boring };
37
38// Roadmap:
39// - add thread pool
40// - add support for parsing the module flags
41// - more fancy transformations
42// - @import inlining of modules
43// - removing statements or blocks of code
44// - replacing operands of `and` and `or` with `true` and `false`
45// - replacing if conditions with `true` and `false`
46// - reduce flags sent to the compiler
47// - integrate with the build system?
48
49pub fn main() !void {
50 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
51 defer arena_instance.deinit();
52 const arena = arena_instance.allocator();
53
54 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
55 const gpa = general_purpose_allocator.allocator();
56
57 const args = try std.process.argsAlloc(arena);
58
59 var opt_checker_path: ?[]const u8 = null;
60 var opt_root_source_file_path: ?[]const u8 = null;
61 var argv: []const []const u8 = &.{};
62 var seed: u32 = 0;
63 var skip_smoke_test = false;
64
65 {
66 var i: usize = 1;
67 while (i < args.len) : (i += 1) {
68 const arg = args[i];
69 if (mem.startsWith(u8, arg, "-")) {
70 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
71 const stdout = std.io.getStdOut().writer();
72 try stdout.writeAll(usage);
73 return std.process.cleanExit();
74 } else if (mem.eql(u8, arg, "--")) {
75 argv = args[i + 1 ..];
76 break;
77 } else if (mem.eql(u8, arg, "--skip-smoke-test")) {
78 skip_smoke_test = true;
79 } else if (mem.eql(u8, arg, "--main-mod-path")) {
80 @panic("TODO: implement --main-mod-path");
81 } else if (mem.eql(u8, arg, "--mod")) {
82 @panic("TODO: implement --mod");
83 } else if (mem.eql(u8, arg, "--deps")) {
84 @panic("TODO: implement --deps");
85 } else if (mem.eql(u8, arg, "--seed")) {
86 i += 1;
87 if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});
88 const next_arg = args[i];
89 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
90 fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{
91 next_arg, @errorName(err),
92 });
93 };
94 } else {
95 fatal("unrecognized parameter: '{s}'", .{arg});
96 }
97 } else if (opt_checker_path == null) {
98 opt_checker_path = arg;
99 } else if (opt_root_source_file_path == null) {
100 opt_root_source_file_path = arg;
101 } else {
102 fatal("unexpected extra parameter: '{s}'", .{arg});
103 }
104 }
105 }
106
107 const checker_path = opt_checker_path orelse
108 fatal("missing interestingness checker argument; see -h for usage", .{});
109 const root_source_file_path = opt_root_source_file_path orelse
110 fatal("missing root source file path argument; see -h for usage", .{});
111
112 var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{};
113 try interestingness_argv.ensureUnusedCapacity(arena, argv.len + 1);
114 interestingness_argv.appendAssumeCapacity(checker_path);
115 interestingness_argv.appendSliceAssumeCapacity(argv);
116
117 var rendered = std.ArrayList(u8).init(gpa);
118 defer rendered.deinit();
119
120 var astgen_input = std.ArrayList(u8).init(gpa);
121 defer astgen_input.deinit();
122
123 var tree = try parse(gpa, root_source_file_path);
124 defer {
125 gpa.free(tree.source);
126 tree.deinit(gpa);
127 }
128
129 if (!skip_smoke_test) {
130 std.debug.print("smoke testing the interestingness check...\n", .{});
131 switch (try runCheck(arena, interestingness_argv.items)) {
132 .interesting => {},
133 .boring, .unknown => |t| {
134 fatal("interestingness check returned {s} for unmodified input\n", .{
135 @tagName(t),
136 });
137 },
138 }
139 }
140
141 var fixups: Ast.Fixups = .{};
142 defer fixups.deinit(gpa);
143
144 var more_fixups: Ast.Fixups = .{};
145 defer more_fixups.deinit(gpa);
146
147 var rng = std.Random.DefaultPrng.init(seed);
148
149 // 1. Walk the AST of the source file looking for independent
150 // reductions and collecting them all into an array list.
151 // 2. Randomize the list of transformations. A future enhancement will add
152 // priority weights to the sorting but for now they are completely
153 // shuffled.
154 // 3. Apply a subset consisting of 1/2 of the transformations and check for
155 // interestingness.
156 // 4. If not interesting, half the subset size again and check again.
157 // 5. Repeat until the subset size is 1, then march the transformation
158 // index forward by 1 with each non-interesting attempt.
159 //
160 // At any point if a subset of transformations succeeds in producing an interesting
161 // result, restart the whole process, reparsing the AST and re-generating the list
162 // of all possible transformations and shuffling it again.
163
164 var transformations = std.ArrayList(Walk.Transformation).init(gpa);
165 defer transformations.deinit();
166 try Walk.findTransformations(arena, &tree, &transformations);
167 sortTransformations(transformations.items, rng.random());
168
169 fresh: while (transformations.items.len > 0) {
170 std.debug.print("found {d} possible transformations\n", .{
171 transformations.items.len,
172 });
173 var subset_size: usize = transformations.items.len;
174 var start_index: usize = 0;
175
176 while (start_index < transformations.items.len) {
177 const prev_subset_size = subset_size;
178 subset_size = @max(1, subset_size * 3 / 4);
179 if (prev_subset_size > 1 and subset_size == 1)
180 start_index = 0;
181
182 const this_set = transformations.items[start_index..][0..subset_size];
183 std.debug.print("trying {d} random transformations: ", .{subset_size});
184 for (this_set[0..@min(this_set.len, 20)]) |t| {
185 std.debug.print("{s} ", .{@tagName(t)});
186 }
187 std.debug.print("\n", .{});
188 try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups);
189
190 rendered.clearRetainingCapacity();
191 try tree.renderToArrayList(&rendered, fixups);
192
193 // The transformations we applied may have resulted in unused locals,
194 // in which case we would like to add the respective discards.
195 {
196 try astgen_input.resize(rendered.items.len);
197 @memcpy(astgen_input.items, rendered.items);
198 try astgen_input.append(0);
199 const source_with_null = astgen_input.items[0 .. astgen_input.items.len - 1 :0];
200 var astgen_tree = try Ast.parse(gpa, source_with_null, .zig);
201 defer astgen_tree.deinit(gpa);
202 if (astgen_tree.errors.len != 0) {
203 @panic("syntax errors occurred");
204 }
205 var zir = try AstGen.generate(gpa, astgen_tree);
206 defer zir.deinit(gpa);
207
208 if (zir.hasCompileErrors()) {
209 more_fixups.clearRetainingCapacity();
210 const payload_index = zir.extra[@intFromEnum(Zir.ExtraIndex.compile_errors)];
211 assert(payload_index != 0);
212 const header = zir.extraData(Zir.Inst.CompileErrors, payload_index);
213 var extra_index = header.end;
214 for (0..header.data.items_len) |_| {
215 const item = zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
216 extra_index = item.end;
217 const msg = zir.nullTerminatedString(item.data.msg);
218 if (mem.eql(u8, msg, "unused local constant") or
219 mem.eql(u8, msg, "unused local variable") or
220 mem.eql(u8, msg, "unused function parameter") or
221 mem.eql(u8, msg, "unused capture"))
222 {
223 const ident_token = item.data.token;
224 try more_fixups.unused_var_decls.put(gpa, ident_token, {});
225 } else {
226 std.debug.print("found other ZIR error: '{s}'\n", .{msg});
227 }
228 }
229 if (more_fixups.count() != 0) {
230 rendered.clearRetainingCapacity();
231 try astgen_tree.renderToArrayList(&rendered, more_fixups);
232 }
233 }
234 }
235
236 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
237 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
238
239 const interestingness = try runCheck(arena, interestingness_argv.items);
240 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
241 subset_size, @tagName(interestingness), start_index, transformations.items.len,
242 });
243 switch (interestingness) {
244 .interesting => {
245 const new_tree = try parse(gpa, root_source_file_path);
246 gpa.free(tree.source);
247 tree.deinit(gpa);
248 tree = new_tree;
249
250 try Walk.findTransformations(arena, &tree, &transformations);
251 sortTransformations(transformations.items, rng.random());
252
253 continue :fresh;
254 },
255 .unknown, .boring => {
256 // Continue to try the next set of transformations.
257 // If we tested only one transformation, move on to the next one.
258 if (subset_size == 1) {
259 start_index += 1;
260 } else {
261 start_index += subset_size;
262 if (start_index + subset_size > transformations.items.len) {
263 start_index = 0;
264 }
265 }
266 },
267 }
268 }
269 std.debug.print("all {d} remaining transformations are uninteresting\n", .{
270 transformations.items.len,
271 });
272
273 // Revert the source back to not be transformed.
274 fixups.clearRetainingCapacity();
275 rendered.clearRetainingCapacity();
276 try tree.renderToArrayList(&rendered, fixups);
277 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
278
279 return std.process.cleanExit();
280 }
281 std.debug.print("no more transformations found\n", .{});
282 return std.process.cleanExit();
283}
284
285fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {
286 rng.shuffle(Walk.Transformation, transformations);
287 // Stable sort based on priority to keep randomness as the secondary sort.
288 // TODO: introduce transformation priorities
289 // std.mem.sort(transformations);
290}
291
292fn termToInteresting(term: std.process.Child.Term) Interestingness {
293 return switch (term) {
294 .Exited => |code| switch (code) {
295 0 => .interesting,
296 1 => .unknown,
297 else => .boring,
298 },
299 else => b: {
300 std.debug.print("interestingness check aborted unexpectedly\n", .{});
301 break :b .boring;
302 },
303 };
304}
305
306fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness {
307 const result = try std.process.Child.run(.{
308 .allocator = arena,
309 .argv = argv,
310 });
311 if (result.stderr.len != 0)
312 std.debug.print("{s}", .{result.stderr});
313 return termToInteresting(result.term);
314}
315
316fn transformationsToFixups(
317 gpa: Allocator,
318 arena: Allocator,
319 root_source_file_path: []const u8,
320 transforms: []const Walk.Transformation,
321 fixups: *Ast.Fixups,
322) !void {
323 fixups.clearRetainingCapacity();
324
325 for (transforms) |t| switch (t) {
326 .gut_function => |fn_decl_node| {
327 try fixups.gut_functions.put(gpa, fn_decl_node, {});
328 },
329 .delete_node => |decl_node| {
330 try fixups.omit_nodes.put(gpa, decl_node, {});
331 },
332 .delete_var_decl => |delete_var_decl| {
333 try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {});
334 for (delete_var_decl.references.items) |ident_node| {
335 try fixups.replace_nodes_with_string.put(gpa, ident_node, "undefined");
336 }
337 },
338 .replace_with_undef => |node| {
339 try fixups.replace_nodes_with_string.put(gpa, node, "undefined");
340 },
341 .replace_with_true => |node| {
342 try fixups.replace_nodes_with_string.put(gpa, node, "true");
343 },
344 .replace_with_false => |node| {
345 try fixups.replace_nodes_with_string.put(gpa, node, "false");
346 },
347 .replace_node => |r| {
348 try fixups.replace_nodes_with_node.put(gpa, r.to_replace, r.replacement);
349 },
350 .inline_imported_file => |inline_imported_file| {
351 const full_imported_path = try std.fs.path.join(gpa, &.{
352 std.fs.path.dirname(root_source_file_path) orelse ".",
353 inline_imported_file.imported_string,
354 });
355 defer gpa.free(full_imported_path);
356 var other_file_ast = try parse(gpa, full_imported_path);
357 defer {
358 gpa.free(other_file_ast.source);
359 other_file_ast.deinit(gpa);
360 }
361
362 var inlined_fixups: Ast.Fixups = .{};
363 defer inlined_fixups.deinit(gpa);
364 if (std.fs.path.dirname(inline_imported_file.imported_string)) |dirname| {
365 inlined_fixups.rebase_imported_paths = dirname;
366 }
367 for (inline_imported_file.in_scope_names.keys()) |name| {
368 // This name needs to be mangled in order to not cause an
369 // ambiguous reference error.
370 var i: u32 = 2;
371 const mangled = while (true) : (i += 1) {
372 const mangled = try std.fmt.allocPrint(gpa, "{s}{d}", .{ name, i });
373 if (!inline_imported_file.in_scope_names.contains(mangled))
374 break mangled;
375 gpa.free(mangled);
376 };
377 try inlined_fixups.rename_identifiers.put(gpa, name, mangled);
378 }
379 defer {
380 for (inlined_fixups.rename_identifiers.values()) |v| {
381 gpa.free(v);
382 }
383 }
384
385 var other_source = std.ArrayList(u8).init(gpa);
386 defer other_source.deinit();
387 try other_source.appendSlice("struct {\n");
388 try other_file_ast.renderToArrayList(&other_source, inlined_fixups);
389 try other_source.appendSlice("}");
390
391 try fixups.replace_nodes_with_string.put(
392 gpa,
393 inline_imported_file.builtin_call_node,
394 try arena.dupe(u8, other_source.items),
395 );
396 },
397 };
398}
399
400fn parse(gpa: Allocator, file_path: []const u8) !Ast {
401 const source_code = std.fs.cwd().readFileAllocOptions(
402 gpa,
403 file_path,
404 std.math.maxInt(u32),
405 null,
406 1,
407 0,
408 ) catch |err| {
409 fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) });
410 };
411 errdefer gpa.free(source_code);
412
413 var tree = try Ast.parse(gpa, source_code, .zig);
414 errdefer tree.deinit(gpa);
415
416 if (tree.errors.len != 0) {
417 @panic("syntax errors occurred");
418 }
419
420 return tree;
421}
422
423fn fatal(comptime format: []const u8, args: anytype) noreturn {
424 std.log.err(format, args);
425 std.process.exit(1);
426}
lib/std/zig/reduce/Walk.zig created+1102
......@@ -0,0 +1,1102 @@
1const std = @import("std");
2const Ast = std.zig.Ast;
3const Walk = @This();
4const assert = std.debug.assert;
5const BuiltinFn = std.zig.BuiltinFn;
6
7ast: *const Ast,
8transformations: *std.ArrayList(Transformation),
9unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
10in_scope_names: std.StringArrayHashMapUnmanaged(u32),
11replace_names: std.StringArrayHashMapUnmanaged(u32),
12gpa: std.mem.Allocator,
13arena: std.mem.Allocator,
14
15pub 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
51pub const Error = error{OutOfMemory};
52
53/// The result will be priority shuffled.
54pub fn findTransformations(
55 arena: std.mem.Allocator,
56 ast: *const Ast,
57 transformations: *std.ArrayList(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
85fn 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
96const ScanDeclsAction = enum { add, remove };
97
98fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void {
99 const ast = w.ast;
100 const gpa = w.gpa;
101 const node_tags = ast.nodes.items(.tag);
102 const main_tokens = ast.nodes.items(.main_token);
103 const token_tags = ast.tokens.items(.tag);
104
105 for (members) |member_node| {
106 const name_token = switch (node_tags[member_node]) {
107 .global_var_decl,
108 .local_var_decl,
109 .simple_var_decl,
110 .aligned_var_decl,
111 => main_tokens[member_node] + 1,
112
113 .fn_proto_simple,
114 .fn_proto_multi,
115 .fn_proto_one,
116 .fn_proto,
117 .fn_decl,
118 => main_tokens[member_node] + 1,
119
120 else => continue,
121 };
122
123 assert(token_tags[name_token] == .identifier);
124 const name_bytes = ast.tokenSlice(name_token);
125
126 switch (action) {
127 .add => {
128 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
129
130 const gop = try w.in_scope_names.getOrPut(gpa, name_bytes);
131 if (!gop.found_existing) gop.value_ptr.* = 0;
132 gop.value_ptr.* += 1;
133 },
134 .remove => {
135 const entry = w.in_scope_names.getEntry(name_bytes).?;
136 if (entry.value_ptr.* <= 1) {
137 assert(w.in_scope_names.swapRemove(name_bytes));
138 } else {
139 entry.value_ptr.* -= 1;
140 }
141 },
142 }
143 }
144}
145
146fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
147 const ast = w.ast;
148 const datas = ast.nodes.items(.data);
149 switch (ast.nodes.items(.tag)[decl]) {
150 .fn_decl => {
151 const fn_proto = datas[decl].lhs;
152 try walkExpression(w, fn_proto);
153 const body_node = datas[decl].rhs;
154 if (!isFnBodyGutted(ast, body_node)) {
155 w.replace_names.clearRetainingCapacity();
156 try w.transformations.append(.{ .gut_function = decl });
157 try walkExpression(w, body_node);
158 }
159 },
160 .fn_proto_simple,
161 .fn_proto_multi,
162 .fn_proto_one,
163 .fn_proto,
164 => {
165 try walkExpression(w, decl);
166 },
167
168 .@"usingnamespace" => {
169 try w.transformations.append(.{ .delete_node = decl });
170 const expr = datas[decl].lhs;
171 try walkExpression(w, expr);
172 },
173
174 .global_var_decl,
175 .local_var_decl,
176 .simple_var_decl,
177 .aligned_var_decl,
178 => try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?),
179
180 .test_decl => {
181 try w.transformations.append(.{ .delete_node = decl });
182 try walkExpression(w, datas[decl].rhs);
183 },
184
185 .container_field_init,
186 .container_field_align,
187 .container_field,
188 => {
189 try w.transformations.append(.{ .delete_node = decl });
190 try walkContainerField(w, ast.fullContainerField(decl).?);
191 },
192
193 .@"comptime" => {
194 try w.transformations.append(.{ .delete_node = decl });
195 try walkExpression(w, decl);
196 },
197
198 .root => unreachable,
199 else => unreachable,
200 }
201}
202
203fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
204 const ast = w.ast;
205 const token_tags = ast.tokens.items(.tag);
206 const main_tokens = ast.nodes.items(.main_token);
207 const node_tags = ast.nodes.items(.tag);
208 const datas = ast.nodes.items(.data);
209 switch (node_tags[node]) {
210 .identifier => {
211 const name_ident = main_tokens[node];
212 assert(token_tags[name_ident] == .identifier);
213 const name_bytes = ast.tokenSlice(name_ident);
214 _ = w.unreferenced_globals.swapRemove(name_bytes);
215 if (w.replace_names.get(name_bytes)) |index| {
216 try w.transformations.items[index].delete_var_decl.references.append(w.arena, node);
217 }
218 },
219
220 .number_literal,
221 .char_literal,
222 .unreachable_literal,
223 .anyframe_literal,
224 .string_literal,
225 => {},
226
227 .multiline_string_literal => {},
228
229 .error_value => {},
230
231 .block_two,
232 .block_two_semicolon,
233 => {
234 const statements = [2]Ast.Node.Index{ datas[node].lhs, datas[node].rhs };
235 if (datas[node].lhs == 0) {
236 return walkBlock(w, node, statements[0..0]);
237 } else if (datas[node].rhs == 0) {
238 return walkBlock(w, node, statements[0..1]);
239 } else {
240 return walkBlock(w, node, statements[0..2]);
241 }
242 },
243 .block,
244 .block_semicolon,
245 => {
246 const statements = ast.extra_data[datas[node].lhs..datas[node].rhs];
247 return walkBlock(w, node, statements);
248 },
249
250 .@"errdefer" => {
251 const expr = datas[node].rhs;
252 return walkExpression(w, expr);
253 },
254
255 .@"defer" => {
256 const expr = datas[node].rhs;
257 return walkExpression(w, expr);
258 },
259 .@"comptime", .@"nosuspend" => {
260 const block = datas[node].lhs;
261 return walkExpression(w, block);
262 },
263
264 .@"suspend" => {
265 const body = datas[node].lhs;
266 return walkExpression(w, body);
267 },
268
269 .@"catch" => {
270 try walkExpression(w, datas[node].lhs); // target
271 try walkExpression(w, datas[node].rhs); // fallback
272 },
273
274 .field_access => {
275 const field_access = datas[node];
276 try walkExpression(w, field_access.lhs);
277 },
278
279 .error_union,
280 .switch_range,
281 => {
282 const infix = datas[node];
283 try walkExpression(w, infix.lhs);
284 return walkExpression(w, infix.rhs);
285 },
286 .for_range => {
287 const infix = datas[node];
288 try walkExpression(w, infix.lhs);
289 if (infix.rhs != 0) {
290 return walkExpression(w, infix.rhs);
291 }
292 },
293
294 .add,
295 .add_wrap,
296 .add_sat,
297 .array_cat,
298 .array_mult,
299 .assign,
300 .assign_bit_and,
301 .assign_bit_or,
302 .assign_shl,
303 .assign_shl_sat,
304 .assign_shr,
305 .assign_bit_xor,
306 .assign_div,
307 .assign_sub,
308 .assign_sub_wrap,
309 .assign_sub_sat,
310 .assign_mod,
311 .assign_add,
312 .assign_add_wrap,
313 .assign_add_sat,
314 .assign_mul,
315 .assign_mul_wrap,
316 .assign_mul_sat,
317 .bang_equal,
318 .bit_and,
319 .bit_or,
320 .shl,
321 .shl_sat,
322 .shr,
323 .bit_xor,
324 .bool_and,
325 .bool_or,
326 .div,
327 .equal_equal,
328 .greater_or_equal,
329 .greater_than,
330 .less_or_equal,
331 .less_than,
332 .merge_error_sets,
333 .mod,
334 .mul,
335 .mul_wrap,
336 .mul_sat,
337 .sub,
338 .sub_wrap,
339 .sub_sat,
340 .@"orelse",
341 => {
342 const infix = datas[node];
343 try walkExpression(w, infix.lhs);
344 try walkExpression(w, infix.rhs);
345 },
346
347 .assign_destructure => {
348 const lhs_count = ast.extra_data[datas[node].lhs];
349 assert(lhs_count > 1);
350 const lhs_exprs = ast.extra_data[datas[node].lhs + 1 ..][0..lhs_count];
351 const rhs = datas[node].rhs;
352
353 for (lhs_exprs) |lhs_node| {
354 switch (node_tags[lhs_node]) {
355 .global_var_decl,
356 .local_var_decl,
357 .simple_var_decl,
358 .aligned_var_decl,
359 => try walkLocalVarDecl(w, ast.fullVarDecl(lhs_node).?),
360
361 else => try walkExpression(w, lhs_node),
362 }
363 }
364 return walkExpression(w, rhs);
365 },
366
367 .bit_not,
368 .bool_not,
369 .negation,
370 .negation_wrap,
371 .optional_type,
372 .address_of,
373 => {
374 return walkExpression(w, datas[node].lhs);
375 },
376
377 .@"try",
378 .@"resume",
379 .@"await",
380 => {
381 return walkExpression(w, datas[node].lhs);
382 },
383
384 .array_type,
385 .array_type_sentinel,
386 => {},
387
388 .ptr_type_aligned,
389 .ptr_type_sentinel,
390 .ptr_type,
391 .ptr_type_bit_range,
392 => {},
393
394 .array_init_one,
395 .array_init_one_comma,
396 .array_init_dot_two,
397 .array_init_dot_two_comma,
398 .array_init_dot,
399 .array_init_dot_comma,
400 .array_init,
401 .array_init_comma,
402 => {
403 var elements: [2]Ast.Node.Index = undefined;
404 return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
405 },
406
407 .struct_init_one,
408 .struct_init_one_comma,
409 .struct_init_dot_two,
410 .struct_init_dot_two_comma,
411 .struct_init_dot,
412 .struct_init_dot_comma,
413 .struct_init,
414 .struct_init_comma,
415 => {
416 var buf: [2]Ast.Node.Index = undefined;
417 return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
418 },
419
420 .call_one,
421 .call_one_comma,
422 .async_call_one,
423 .async_call_one_comma,
424 .call,
425 .call_comma,
426 .async_call,
427 .async_call_comma,
428 => {
429 var buf: [1]Ast.Node.Index = undefined;
430 return walkCall(w, ast.fullCall(&buf, node).?);
431 },
432
433 .array_access => {
434 const suffix = datas[node];
435 try walkExpression(w, suffix.lhs);
436 try walkExpression(w, suffix.rhs);
437 },
438
439 .slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
440
441 .deref => {
442 try walkExpression(w, datas[node].lhs);
443 },
444
445 .unwrap_optional => {
446 try walkExpression(w, datas[node].lhs);
447 },
448
449 .@"break" => {
450 const label_token = datas[node].lhs;
451 const target = datas[node].rhs;
452 if (label_token == 0 and target == 0) {
453 // no expressions
454 } else if (label_token == 0 and target != 0) {
455 try walkExpression(w, target);
456 } else if (label_token != 0 and target == 0) {
457 try walkIdentifier(w, label_token);
458 } else if (label_token != 0 and target != 0) {
459 try walkExpression(w, target);
460 }
461 },
462
463 .@"continue" => {
464 const label = datas[node].lhs;
465 if (label != 0) {
466 return walkIdentifier(w, label); // label
467 }
468 },
469
470 .@"return" => {
471 if (datas[node].lhs != 0) {
472 try walkExpression(w, datas[node].lhs);
473 }
474 },
475
476 .grouped_expression => {
477 try walkExpression(w, datas[node].lhs);
478 },
479
480 .container_decl,
481 .container_decl_trailing,
482 .container_decl_arg,
483 .container_decl_arg_trailing,
484 .container_decl_two,
485 .container_decl_two_trailing,
486 .tagged_union,
487 .tagged_union_trailing,
488 .tagged_union_enum_tag,
489 .tagged_union_enum_tag_trailing,
490 .tagged_union_two,
491 .tagged_union_two_trailing,
492 => {
493 var buf: [2]Ast.Node.Index = undefined;
494 return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
495 },
496
497 .error_set_decl => {
498 const error_token = main_tokens[node];
499 const lbrace = error_token + 1;
500 const rbrace = datas[node].rhs;
501
502 var i = lbrace + 1;
503 while (i < rbrace) : (i += 1) {
504 switch (token_tags[i]) {
505 .doc_comment => unreachable, // TODO
506 .identifier => try walkIdentifier(w, i),
507 .comma => {},
508 else => unreachable,
509 }
510 }
511 },
512
513 .builtin_call_two, .builtin_call_two_comma => {
514 if (datas[node].lhs == 0) {
515 return walkBuiltinCall(w, node, &.{});
516 } else if (datas[node].rhs == 0) {
517 return walkBuiltinCall(w, node, &.{datas[node].lhs});
518 } else {
519 return walkBuiltinCall(w, node, &.{ datas[node].lhs, datas[node].rhs });
520 }
521 },
522 .builtin_call, .builtin_call_comma => {
523 const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
524 return walkBuiltinCall(w, node, params);
525 },
526
527 .fn_proto_simple,
528 .fn_proto_multi,
529 .fn_proto_one,
530 .fn_proto,
531 => {
532 var buf: [1]Ast.Node.Index = undefined;
533 return walkFnProto(w, ast.fullFnProto(&buf, node).?);
534 },
535
536 .anyframe_type => {
537 if (datas[node].rhs != 0) {
538 return walkExpression(w, datas[node].rhs);
539 }
540 },
541
542 .@"switch",
543 .switch_comma,
544 => {
545 const condition = datas[node].lhs;
546 const extra = ast.extraData(datas[node].rhs, Ast.Node.SubRange);
547 const cases = ast.extra_data[extra.start..extra.end];
548
549 try walkExpression(w, condition); // condition expression
550 try walkExpressions(w, cases);
551 },
552
553 .switch_case_one,
554 .switch_case_inline_one,
555 .switch_case,
556 .switch_case_inline,
557 => return walkSwitchCase(w, ast.fullSwitchCase(node).?),
558
559 .while_simple,
560 .while_cont,
561 .@"while",
562 => return walkWhile(w, node, ast.fullWhile(node).?),
563
564 .for_simple,
565 .@"for",
566 => return walkFor(w, ast.fullFor(node).?),
567
568 .if_simple,
569 .@"if",
570 => return walkIf(w, node, ast.fullIf(node).?),
571
572 .asm_simple,
573 .@"asm",
574 => return walkAsm(w, ast.fullAsm(node).?),
575
576 .enum_literal => {
577 return walkIdentifier(w, main_tokens[node]); // name
578 },
579
580 .fn_decl => unreachable,
581 .container_field => unreachable,
582 .container_field_init => unreachable,
583 .container_field_align => unreachable,
584 .root => unreachable,
585 .global_var_decl => unreachable,
586 .local_var_decl => unreachable,
587 .simple_var_decl => unreachable,
588 .aligned_var_decl => unreachable,
589 .@"usingnamespace" => unreachable,
590 .test_decl => unreachable,
591 .asm_output => unreachable,
592 .asm_input => unreachable,
593 }
594}
595
596fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void {
597 _ = decl_node;
598
599 if (var_decl.ast.type_node != 0) {
600 try walkExpression(w, var_decl.ast.type_node);
601 }
602
603 if (var_decl.ast.align_node != 0) {
604 try walkExpression(w, var_decl.ast.align_node);
605 }
606
607 if (var_decl.ast.addrspace_node != 0) {
608 try walkExpression(w, var_decl.ast.addrspace_node);
609 }
610
611 if (var_decl.ast.section_node != 0) {
612 try walkExpression(w, var_decl.ast.section_node);
613 }
614
615 if (var_decl.ast.init_node != 0) {
616 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
617 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
618 }
619 try walkExpression(w, var_decl.ast.init_node);
620 }
621}
622
623fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
624 try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name
625
626 if (var_decl.ast.type_node != 0) {
627 try walkExpression(w, var_decl.ast.type_node);
628 }
629
630 if (var_decl.ast.align_node != 0) {
631 try walkExpression(w, var_decl.ast.align_node);
632 }
633
634 if (var_decl.ast.addrspace_node != 0) {
635 try walkExpression(w, var_decl.ast.addrspace_node);
636 }
637
638 if (var_decl.ast.section_node != 0) {
639 try walkExpression(w, var_decl.ast.section_node);
640 }
641
642 if (var_decl.ast.init_node != 0) {
643 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
644 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
645 }
646 try walkExpression(w, var_decl.ast.init_node);
647 }
648}
649
650fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
651 if (field.ast.type_expr != 0) {
652 try walkExpression(w, field.ast.type_expr); // type
653 }
654 if (field.ast.align_expr != 0) {
655 try walkExpression(w, field.ast.align_expr); // alignment
656 }
657 if (field.ast.value_expr != 0) {
658 try walkExpression(w, field.ast.value_expr); // value
659 }
660}
661
662fn walkBlock(
663 w: *Walk,
664 block_node: Ast.Node.Index,
665 statements: []const Ast.Node.Index,
666) Error!void {
667 _ = block_node;
668 const ast = w.ast;
669 const node_tags = ast.nodes.items(.tag);
670
671 for (statements) |stmt| {
672 switch (node_tags[stmt]) {
673 .global_var_decl,
674 .local_var_decl,
675 .simple_var_decl,
676 .aligned_var_decl,
677 => {
678 const var_decl = ast.fullVarDecl(stmt).?;
679 if (var_decl.ast.init_node != 0 and
680 isUndefinedIdent(w.ast, var_decl.ast.init_node))
681 {
682 try w.transformations.append(.{ .delete_var_decl = .{
683 .var_decl_node = stmt,
684 .references = .{},
685 } });
686 const name_tok = var_decl.ast.mut_token + 1;
687 const name_bytes = ast.tokenSlice(name_tok);
688 try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1));
689 } else {
690 try walkLocalVarDecl(w, var_decl);
691 }
692 },
693
694 else => {
695 switch (categorizeStmt(ast, stmt)) {
696 // Don't try to remove `_ = foo;` discards; those are handled separately.
697 .discard_identifier => {},
698 // definitely try to remove `_ = undefined;` though.
699 .discard_undefined, .trap_call, .other => {
700 try w.transformations.append(.{ .delete_node = stmt });
701 },
702 }
703 try walkExpression(w, stmt);
704 },
705 }
706 }
707}
708
709fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
710 try walkExpression(w, array_type.ast.elem_count);
711 if (array_type.ast.sentinel != 0) {
712 try walkExpression(w, array_type.ast.sentinel);
713 }
714 return walkExpression(w, array_type.ast.elem_type);
715}
716
717fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
718 if (array_init.ast.type_expr != 0) {
719 try walkExpression(w, array_init.ast.type_expr); // T
720 }
721 for (array_init.ast.elements) |elem_init| {
722 try walkExpression(w, elem_init);
723 }
724}
725
726fn walkStructInit(
727 w: *Walk,
728 struct_node: Ast.Node.Index,
729 struct_init: Ast.full.StructInit,
730) Error!void {
731 _ = struct_node;
732 if (struct_init.ast.type_expr != 0) {
733 try walkExpression(w, struct_init.ast.type_expr); // T
734 }
735 for (struct_init.ast.fields) |field_init| {
736 try walkExpression(w, field_init);
737 }
738}
739
740fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
741 try walkExpression(w, call.ast.fn_expr);
742 try walkParamList(w, call.ast.params);
743}
744
745fn walkSlice(
746 w: *Walk,
747 slice_node: Ast.Node.Index,
748 slice: Ast.full.Slice,
749) Error!void {
750 _ = slice_node;
751 try walkExpression(w, slice.ast.sliced);
752 try walkExpression(w, slice.ast.start);
753 if (slice.ast.end != 0) {
754 try walkExpression(w, slice.ast.end);
755 }
756 if (slice.ast.sentinel != 0) {
757 try walkExpression(w, slice.ast.sentinel);
758 }
759}
760
761fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
762 const ast = w.ast;
763 const token_tags = ast.tokens.items(.tag);
764 assert(token_tags[name_ident] == .identifier);
765 const name_bytes = ast.tokenSlice(name_ident);
766 _ = w.unreferenced_globals.swapRemove(name_bytes);
767}
768
769fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
770 _ = w;
771 _ = name_ident;
772}
773
774fn walkContainerDecl(
775 w: *Walk,
776 container_decl_node: Ast.Node.Index,
777 container_decl: Ast.full.ContainerDecl,
778) Error!void {
779 _ = container_decl_node;
780 if (container_decl.ast.arg != 0) {
781 try walkExpression(w, container_decl.ast.arg);
782 }
783 try walkMembers(w, container_decl.ast.members);
784}
785
786fn walkBuiltinCall(
787 w: *Walk,
788 call_node: Ast.Node.Index,
789 params: []const Ast.Node.Index,
790) Error!void {
791 const ast = w.ast;
792 const main_tokens = ast.nodes.items(.main_token);
793 const builtin_token = main_tokens[call_node];
794 const builtin_name = ast.tokenSlice(builtin_token);
795 const info = BuiltinFn.list.get(builtin_name).?;
796 switch (info.tag) {
797 .import => {
798 const operand_node = params[0];
799 const str_lit_token = main_tokens[operand_node];
800 const token_bytes = ast.tokenSlice(str_lit_token);
801 if (std.mem.endsWith(u8, token_bytes, ".zig\"")) {
802 const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch
803 unreachable;
804 try w.transformations.append(.{ .inline_imported_file = .{
805 .builtin_call_node = call_node,
806 .imported_string = imported_string,
807 .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init(
808 w.arena,
809 w.in_scope_names.keys(),
810 &.{},
811 ),
812 } });
813 }
814 },
815 else => {},
816 }
817 for (params) |param_node| {
818 try walkExpression(w, param_node);
819 }
820}
821
822fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
823 const ast = w.ast;
824
825 {
826 var it = fn_proto.iterate(ast);
827 while (it.next()) |param| {
828 if (param.type_expr != 0) {
829 try walkExpression(w, param.type_expr);
830 }
831 }
832 }
833
834 if (fn_proto.ast.align_expr != 0) {
835 try walkExpression(w, fn_proto.ast.align_expr);
836 }
837
838 if (fn_proto.ast.addrspace_expr != 0) {
839 try walkExpression(w, fn_proto.ast.addrspace_expr);
840 }
841
842 if (fn_proto.ast.section_expr != 0) {
843 try walkExpression(w, fn_proto.ast.section_expr);
844 }
845
846 if (fn_proto.ast.callconv_expr != 0) {
847 try walkExpression(w, fn_proto.ast.callconv_expr);
848 }
849
850 try walkExpression(w, fn_proto.ast.return_type);
851}
852
853fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
854 for (expressions) |expression| {
855 try walkExpression(w, expression);
856 }
857}
858
859fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
860 for (switch_case.ast.values) |value_expr| {
861 try walkExpression(w, value_expr);
862 }
863 try walkExpression(w, switch_case.ast.target_expr);
864}
865
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
897 try walkExpression(w, while_node.ast.cond_expr); // condition
898
899 if (while_node.ast.cont_expr != 0) {
900 try walkExpression(w, while_node.ast.cont_expr);
901 }
902
903 if (while_node.ast.then_expr != 0) {
904 try walkExpression(w, while_node.ast.then_expr);
905 }
906 if (while_node.ast.else_expr != 0) {
907 try walkExpression(w, while_node.ast.else_expr);
908 }
909}
910
911fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
912 try walkParamList(w, for_node.ast.inputs);
913 if (for_node.ast.then_expr != 0) {
914 try walkExpression(w, for_node.ast.then_expr);
915 }
916 if (for_node.ast.else_expr != 0) {
917 try walkExpression(w, for_node.ast.else_expr);
918 }
919}
920
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
952 try walkExpression(w, if_node.ast.cond_expr); // condition
953
954 if (if_node.ast.then_expr != 0) {
955 try walkExpression(w, if_node.ast.then_expr);
956 }
957 if (if_node.ast.else_expr != 0) {
958 try walkExpression(w, if_node.ast.else_expr);
959 }
960}
961
962fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
963 try walkExpression(w, asm_node.ast.template);
964 for (asm_node.ast.items) |item| {
965 try walkExpression(w, item);
966 }
967}
968
969fn walkParamList(w: *Walk, params: []const Ast.Node.Index) Error!void {
970 for (params) |param_node| {
971 try walkExpression(w, param_node);
972 }
973}
974
975/// Check if it is already gutted (i.e. its body replaced with `@trap()`).
976fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
977 // skip over discards
978 const node_tags = ast.nodes.items(.tag);
979 const datas = ast.nodes.items(.data);
980 var statements_buf: [2]Ast.Node.Index = undefined;
981 const statements = switch (node_tags[body_node]) {
982 .block_two,
983 .block_two_semicolon,
984 => blk: {
985 statements_buf[0..2].* = .{ datas[body_node].lhs, datas[body_node].rhs };
986 break :blk if (datas[body_node].lhs == 0)
987 statements_buf[0..0]
988 else if (datas[body_node].rhs == 0)
989 statements_buf[0..1]
990 else
991 statements_buf[0..2];
992 },
993
994 .block,
995 .block_semicolon,
996 => ast.extra_data[datas[body_node].lhs..datas[body_node].rhs],
997
998 else => return false,
999 };
1000 var i: usize = 0;
1001 while (i < statements.len) : (i += 1) {
1002 switch (categorizeStmt(ast, statements[i])) {
1003 .discard_identifier => continue,
1004 .trap_call => return i + 1 == statements.len,
1005 else => return false,
1006 }
1007 }
1008 return false;
1009}
1010
1011const StmtCategory = enum {
1012 discard_undefined,
1013 discard_identifier,
1014 trap_call,
1015 other,
1016};
1017
1018fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
1019 const node_tags = ast.nodes.items(.tag);
1020 const datas = ast.nodes.items(.data);
1021 const main_tokens = ast.nodes.items(.main_token);
1022 switch (node_tags[stmt]) {
1023 .builtin_call_two, .builtin_call_two_comma => {
1024 if (datas[stmt].lhs == 0) {
1025 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{});
1026 } else if (datas[stmt].rhs == 0) {
1027 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{datas[stmt].lhs});
1028 } else {
1029 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{ datas[stmt].lhs, datas[stmt].rhs });
1030 }
1031 },
1032 .builtin_call, .builtin_call_comma => {
1033 const params = ast.extra_data[datas[stmt].lhs..datas[stmt].rhs];
1034 return categorizeBuiltinCall(ast, main_tokens[stmt], params);
1035 },
1036 .assign => {
1037 const infix = datas[stmt];
1038 if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier) {
1039 const name_bytes = ast.tokenSlice(main_tokens[infix.rhs]);
1040 if (std.mem.eql(u8, name_bytes, "undefined")) {
1041 return .discard_undefined;
1042 } else {
1043 return .discard_identifier;
1044 }
1045 }
1046 return .other;
1047 },
1048 else => return .other,
1049 }
1050}
1051
1052fn categorizeBuiltinCall(
1053 ast: *const Ast,
1054 builtin_token: Ast.TokenIndex,
1055 params: []const Ast.Node.Index,
1056) StmtCategory {
1057 if (params.len != 0) return .other;
1058 const name_bytes = ast.tokenSlice(builtin_token);
1059 if (std.mem.eql(u8, name_bytes, "@trap"))
1060 return .trap_call;
1061 return .other;
1062}
1063
1064fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1065 return isMatchingIdent(ast, node, "_");
1066}
1067
1068fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1069 return isMatchingIdent(ast, node, "undefined");
1070}
1071
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
1080fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
1081 const node_tags = ast.nodes.items(.tag);
1082 const main_tokens = ast.nodes.items(.main_token);
1083 switch (node_tags[node]) {
1084 .identifier => {
1085 const token_index = main_tokens[node];
1086 const name_bytes = ast.tokenSlice(token_index);
1087 return std.mem.eql(u8, name_bytes, string);
1088 },
1089 else => return false,
1090 }
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}
src/main.zig+14-16
......@@ -203,14 +203,6 @@ pub fn main() anyerror!void {
203203 }
204204 }
205205
206 if (build_options.only_reduce) {
207 if (mem.eql(u8, args[1], "reduce")) {
208 return @import("reduce.zig").main(gpa, arena, args);
209 } else {
210 @panic("only reduce is supported in a -Donly-reduce build");
211 }
212 }
213
214206 return mainArgs(gpa, arena, args);
215207}
216208
......@@ -302,7 +294,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
302294 } else if (mem.eql(u8, cmd, "rc")) {
303295 return cmdRc(gpa, arena, args[1..]);
304296 } else if (mem.eql(u8, cmd, "fmt")) {
305 return cmdFmt(gpa, arena, cmd_args);
297 return jitCmd(gpa, arena, cmd_args, "fmt", "fmt.zig");
306298 } else if (mem.eql(u8, cmd, "objcopy")) {
307299 return @import("objcopy.zig").cmdObjCopy(gpa, arena, cmd_args);
308300 } else if (mem.eql(u8, cmd, "fetch")) {
......@@ -325,7 +317,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
325317 verifyLibcxxCorrectlyLinked();
326318 return @import("print_env.zig").cmdEnv(arena, cmd_args, io.getStdOut().writer());
327319 } else if (mem.eql(u8, cmd, "reduce")) {
328 return @import("reduce.zig").main(gpa, arena, args);
320 return jitCmd(gpa, arena, cmd_args, "reduce", "reduce.zig");
329321 } else if (mem.eql(u8, cmd, "zen")) {
330322 return io.getStdOut().writeAll(info_zen);
331323 } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) {
......@@ -5710,7 +5702,13 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
57105702 }
57115703}
57125704
5713fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5705fn jitCmd(
5706 gpa: Allocator,
5707 arena: Allocator,
5708 args: []const []const u8,
5709 cmd_name: []const u8,
5710 root_src_path: []const u8,
5711) !void {
57145712 const color: Color = .auto;
57155713
57165714 const target_query: std.Target.Query = .{};
......@@ -5721,7 +5719,7 @@ fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
57215719 };
57225720
57235721 const exe_basename = try std.zig.binNameAlloc(arena, .{
5724 .root_name = "fmt",
5722 .root_name = cmd_name,
57255723 .target = resolved_target.result,
57265724 .output_mode = .Exe,
57275725 });
......@@ -5771,7 +5769,7 @@ fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
57715769 .root_dir = zig_lib_directory,
57725770 .sub_path = "std/zig",
57735771 },
5774 .root_src_path = "fmt.zig",
5772 .root_src_path = root_src_path,
57755773 };
57765774
57775775 const config = try Compilation.Config.resolve(.{
......@@ -5801,7 +5799,7 @@ fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
58015799 .zig_lib_directory = zig_lib_directory,
58025800 .local_cache_directory = global_cache_directory,
58035801 .global_cache_directory = global_cache_directory,
5804 .root_name = "fmt",
5802 .root_name = cmd_name,
58055803 .config = config,
58065804 .root_mod = root_mod,
58075805 .main_mod = root_mod,
......@@ -5820,8 +5818,8 @@ fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
58205818 else => |e| return e,
58215819 };
58225820
5823 const fmt_exe = try global_cache_directory.join(arena, &.{comp.cache_use.whole.bin_sub_path.?});
5824 child_argv.appendAssumeCapacity(fmt_exe);
5821 const exe_path = try global_cache_directory.join(arena, &.{comp.cache_use.whole.bin_sub_path.?});
5822 child_argv.appendAssumeCapacity(exe_path);
58255823 }
58265824
58275825 child_argv.appendSliceAssumeCapacity(args);
src/reduce.zig deleted-413
......@@ -1,413 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5const fatal = @import("./main.zig").fatal;
6const Ast = std.zig.Ast;
7const Walk = @import("reduce/Walk.zig");
8const AstGen = std.zig.AstGen;
9const Zir = std.zig.Zir;
10
11const usage =
12 \\zig reduce [options] ./checker root_source_file.zig [-- [argv]]
13 \\
14 \\root_source_file.zig is relative to --main-mod-path.
15 \\
16 \\checker:
17 \\ An executable that communicates interestingness by returning these exit codes:
18 \\ exit(0): interesting
19 \\ exit(1): unknown (infinite loop or other mishap)
20 \\ exit(other): not interesting
21 \\
22 \\options:
23 \\ --seed [integer] Override the random seed. Defaults to 0
24 \\ --skip-smoke-test Skip interestingness check smoke test
25 \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name
26 \\ deps: [dep],[dep],...
27 \\ dep: [[import=]name]
28 \\ --deps [dep],[dep],... Set dependency names for the root package
29 \\ dep: [[import=]name]
30 \\ --main-mod-path Set the directory of the root module
31 \\
32 \\argv:
33 \\ Forwarded directly to the interestingness script.
34 \\
35;
36
37const Interestingness = enum { interesting, unknown, boring };
38
39// Roadmap:
40// - add thread pool
41// - add support for parsing the module flags
42// - more fancy transformations
43// - @import inlining of modules
44// - removing statements or blocks of code
45// - replacing operands of `and` and `or` with `true` and `false`
46// - replacing if conditions with `true` and `false`
47// - reduce flags sent to the compiler
48// - integrate with the build system?
49
50pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
51 var opt_checker_path: ?[]const u8 = null;
52 var opt_root_source_file_path: ?[]const u8 = null;
53 var argv: []const []const u8 = &.{};
54 var seed: u32 = 0;
55 var skip_smoke_test = false;
56
57 {
58 var i: usize = 2; // skip over "zig" and "reduce"
59 while (i < args.len) : (i += 1) {
60 const arg = args[i];
61 if (mem.startsWith(u8, arg, "-")) {
62 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
63 const stdout = std.io.getStdOut().writer();
64 try stdout.writeAll(usage);
65 return std.process.cleanExit();
66 } else if (mem.eql(u8, arg, "--")) {
67 argv = args[i + 1 ..];
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 };
86 } else {
87 fatal("unrecognized parameter: '{s}'", .{arg});
88 }
89 } else if (opt_checker_path == null) {
90 opt_checker_path = arg;
91 } else if (opt_root_source_file_path == null) {
92 opt_root_source_file_path = arg;
93 } else {
94 fatal("unexpected extra parameter: '{s}'", .{arg});
95 }
96 }
97 }
98
99 const checker_path = opt_checker_path orelse
100 fatal("missing interestingness checker argument; see -h for usage", .{});
101 const root_source_file_path = opt_root_source_file_path orelse
102 fatal("missing root source file path argument; see -h for usage", .{});
103
104 var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{};
105 try interestingness_argv.ensureUnusedCapacity(arena, argv.len + 1);
106 interestingness_argv.appendAssumeCapacity(checker_path);
107 interestingness_argv.appendSliceAssumeCapacity(argv);
108
109 var rendered = std.ArrayList(u8).init(gpa);
110 defer rendered.deinit();
111
112 var astgen_input = std.ArrayList(u8).init(gpa);
113 defer astgen_input.deinit();
114
115 var tree = try parse(gpa, root_source_file_path);
116 defer {
117 gpa.free(tree.source);
118 tree.deinit(gpa);
119 }
120
121 if (!skip_smoke_test) {
122 std.debug.print("smoke testing the interestingness check...\n", .{});
123 switch (try runCheck(arena, interestingness_argv.items)) {
124 .interesting => {},
125 .boring, .unknown => |t| {
126 fatal("interestingness check returned {s} for unmodified input\n", .{
127 @tagName(t),
128 });
129 },
130 }
131 }
132
133 var fixups: Ast.Fixups = .{};
134 defer fixups.deinit(gpa);
135
136 var more_fixups: Ast.Fixups = .{};
137 defer more_fixups.deinit(gpa);
138
139 var rng = std.Random.DefaultPrng.init(seed);
140
141 // 1. Walk the AST of the source file looking for independent
142 // reductions and collecting them all into an array list.
143 // 2. Randomize the list of transformations. A future enhancement will add
144 // priority weights to the sorting but for now they are completely
145 // shuffled.
146 // 3. Apply a subset consisting of 1/2 of the transformations and check for
147 // interestingness.
148 // 4. If not interesting, half the subset size again and check again.
149 // 5. Repeat until the subset size is 1, then march the transformation
150 // index forward by 1 with each non-interesting attempt.
151 //
152 // At any point if a subset of transformations succeeds in producing an interesting
153 // result, restart the whole process, reparsing the AST and re-generating the list
154 // of all possible transformations and shuffling it again.
155
156 var transformations = std.ArrayList(Walk.Transformation).init(gpa);
157 defer transformations.deinit();
158 try Walk.findTransformations(arena, &tree, &transformations);
159 sortTransformations(transformations.items, rng.random());
160
161 fresh: while (transformations.items.len > 0) {
162 std.debug.print("found {d} possible transformations\n", .{
163 transformations.items.len,
164 });
165 var subset_size: usize = transformations.items.len;
166 var start_index: usize = 0;
167
168 while (start_index < transformations.items.len) {
169 const prev_subset_size = subset_size;
170 subset_size = @max(1, subset_size * 3 / 4);
171 if (prev_subset_size > 1 and subset_size == 1)
172 start_index = 0;
173
174 const this_set = transformations.items[start_index..][0..subset_size];
175 std.debug.print("trying {d} random transformations: ", .{subset_size});
176 for (this_set[0..@min(this_set.len, 20)]) |t| {
177 std.debug.print("{s} ", .{@tagName(t)});
178 }
179 std.debug.print("\n", .{});
180 try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups);
181
182 rendered.clearRetainingCapacity();
183 try tree.renderToArrayList(&rendered, fixups);
184
185 // The transformations we applied may have resulted in unused locals,
186 // in which case we would like to add the respective discards.
187 {
188 try astgen_input.resize(rendered.items.len);
189 @memcpy(astgen_input.items, rendered.items);
190 try astgen_input.append(0);
191 const source_with_null = astgen_input.items[0 .. astgen_input.items.len - 1 :0];
192 var astgen_tree = try Ast.parse(gpa, source_with_null, .zig);
193 defer astgen_tree.deinit(gpa);
194 if (astgen_tree.errors.len != 0) {
195 @panic("syntax errors occurred");
196 }
197 var zir = try AstGen.generate(gpa, astgen_tree);
198 defer zir.deinit(gpa);
199
200 if (zir.hasCompileErrors()) {
201 more_fixups.clearRetainingCapacity();
202 const payload_index = zir.extra[@intFromEnum(Zir.ExtraIndex.compile_errors)];
203 assert(payload_index != 0);
204 const header = zir.extraData(Zir.Inst.CompileErrors, payload_index);
205 var extra_index = header.end;
206 for (0..header.data.items_len) |_| {
207 const item = zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
208 extra_index = item.end;
209 const msg = zir.nullTerminatedString(item.data.msg);
210 if (mem.eql(u8, msg, "unused local constant") or
211 mem.eql(u8, msg, "unused local variable") or
212 mem.eql(u8, msg, "unused function parameter") or
213 mem.eql(u8, msg, "unused capture"))
214 {
215 const ident_token = item.data.token;
216 try more_fixups.unused_var_decls.put(gpa, ident_token, {});
217 } else {
218 std.debug.print("found other ZIR error: '{s}'\n", .{msg});
219 }
220 }
221 if (more_fixups.count() != 0) {
222 rendered.clearRetainingCapacity();
223 try astgen_tree.renderToArrayList(&rendered, more_fixups);
224 }
225 }
226 }
227
228 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
229 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
230
231 const interestingness = try runCheck(arena, interestingness_argv.items);
232 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
233 subset_size, @tagName(interestingness), start_index, transformations.items.len,
234 });
235 switch (interestingness) {
236 .interesting => {
237 const new_tree = try parse(gpa, root_source_file_path);
238 gpa.free(tree.source);
239 tree.deinit(gpa);
240 tree = new_tree;
241
242 try Walk.findTransformations(arena, &tree, &transformations);
243 sortTransformations(transformations.items, rng.random());
244
245 continue :fresh;
246 },
247 .unknown, .boring => {
248 // Continue to try the next set of transformations.
249 // If we tested only one transformation, move on to the next one.
250 if (subset_size == 1) {
251 start_index += 1;
252 } else {
253 start_index += subset_size;
254 if (start_index + subset_size > transformations.items.len) {
255 start_index = 0;
256 }
257 }
258 },
259 }
260 }
261 std.debug.print("all {d} remaining transformations are uninteresting\n", .{
262 transformations.items.len,
263 });
264
265 // Revert the source back to not be transformed.
266 fixups.clearRetainingCapacity();
267 rendered.clearRetainingCapacity();
268 try tree.renderToArrayList(&rendered, fixups);
269 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
270
271 return std.process.cleanExit();
272 }
273 std.debug.print("no more transformations found\n", .{});
274 return std.process.cleanExit();
275}
276
277fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {
278 rng.shuffle(Walk.Transformation, transformations);
279 // Stable sort based on priority to keep randomness as the secondary sort.
280 // TODO: introduce transformation priorities
281 // std.mem.sort(transformations);
282}
283
284fn termToInteresting(term: std.process.Child.Term) Interestingness {
285 return switch (term) {
286 .Exited => |code| switch (code) {
287 0 => .interesting,
288 1 => .unknown,
289 else => .boring,
290 },
291 else => b: {
292 std.debug.print("interestingness check aborted unexpectedly\n", .{});
293 break :b .boring;
294 },
295 };
296}
297
298fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness {
299 const result = try std.process.Child.run(.{
300 .allocator = arena,
301 .argv = argv,
302 });
303 if (result.stderr.len != 0)
304 std.debug.print("{s}", .{result.stderr});
305 return termToInteresting(result.term);
306}
307
308fn transformationsToFixups(
309 gpa: Allocator,
310 arena: Allocator,
311 root_source_file_path: []const u8,
312 transforms: []const Walk.Transformation,
313 fixups: *Ast.Fixups,
314) !void {
315 fixups.clearRetainingCapacity();
316
317 for (transforms) |t| switch (t) {
318 .gut_function => |fn_decl_node| {
319 try fixups.gut_functions.put(gpa, fn_decl_node, {});
320 },
321 .delete_node => |decl_node| {
322 try fixups.omit_nodes.put(gpa, decl_node, {});
323 },
324 .delete_var_decl => |delete_var_decl| {
325 try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {});
326 for (delete_var_decl.references.items) |ident_node| {
327 try fixups.replace_nodes_with_string.put(gpa, ident_node, "undefined");
328 }
329 },
330 .replace_with_undef => |node| {
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);
341 },
342 .inline_imported_file => |inline_imported_file| {
343 const full_imported_path = try std.fs.path.join(gpa, &.{
344 std.fs.path.dirname(root_source_file_path) orelse ".",
345 inline_imported_file.imported_string,
346 });
347 defer gpa.free(full_imported_path);
348 var other_file_ast = try parse(gpa, full_imported_path);
349 defer {
350 gpa.free(other_file_ast.source);
351 other_file_ast.deinit(gpa);
352 }
353
354 var inlined_fixups: Ast.Fixups = .{};
355 defer inlined_fixups.deinit(gpa);
356 if (std.fs.path.dirname(inline_imported_file.imported_string)) |dirname| {
357 inlined_fixups.rebase_imported_paths = dirname;
358 }
359 for (inline_imported_file.in_scope_names.keys()) |name| {
360 // This name needs to be mangled in order to not cause an
361 // ambiguous reference error.
362 var i: u32 = 2;
363 const mangled = while (true) : (i += 1) {
364 const mangled = try std.fmt.allocPrint(gpa, "{s}{d}", .{ name, i });
365 if (!inline_imported_file.in_scope_names.contains(mangled))
366 break mangled;
367 gpa.free(mangled);
368 };
369 try inlined_fixups.rename_identifiers.put(gpa, name, mangled);
370 }
371 defer {
372 for (inlined_fixups.rename_identifiers.values()) |v| {
373 gpa.free(v);
374 }
375 }
376
377 var other_source = std.ArrayList(u8).init(gpa);
378 defer other_source.deinit();
379 try other_source.appendSlice("struct {\n");
380 try other_file_ast.renderToArrayList(&other_source, inlined_fixups);
381 try other_source.appendSlice("}");
382
383 try fixups.replace_nodes_with_string.put(
384 gpa,
385 inline_imported_file.builtin_call_node,
386 try arena.dupe(u8, other_source.items),
387 );
388 },
389 };
390}
391
392fn parse(gpa: Allocator, file_path: []const u8) !Ast {
393 const source_code = std.fs.cwd().readFileAllocOptions(
394 gpa,
395 file_path,
396 std.math.maxInt(u32),
397 null,
398 1,
399 0,
400 ) catch |err| {
401 fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) });
402 };
403 errdefer gpa.free(source_code);
404
405 var tree = try Ast.parse(gpa, source_code, .zig);
406 errdefer tree.deinit(gpa);
407
408 if (tree.errors.len != 0) {
409 @panic("syntax errors occurred");
410 }
411
412 return tree;
413}
src/reduce/Walk.zig deleted-1102
......@@ -1,1102 +0,0 @@
1const std = @import("std");
2const Ast = std.zig.Ast;
3const Walk = @This();
4const assert = std.debug.assert;
5const BuiltinFn = std.zig.BuiltinFn;
6
7ast: *const Ast,
8transformations: *std.ArrayList(Transformation),
9unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
10in_scope_names: std.StringArrayHashMapUnmanaged(u32),
11replace_names: std.StringArrayHashMapUnmanaged(u32),
12gpa: std.mem.Allocator,
13arena: std.mem.Allocator,
14
15pub 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
51pub const Error = error{OutOfMemory};
52
53/// The result will be priority shuffled.
54pub fn findTransformations(
55 arena: std.mem.Allocator,
56 ast: *const Ast,
57 transformations: *std.ArrayList(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
85fn 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
96const ScanDeclsAction = enum { add, remove };
97
98fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void {
99 const ast = w.ast;
100 const gpa = w.gpa;
101 const node_tags = ast.nodes.items(.tag);
102 const main_tokens = ast.nodes.items(.main_token);
103 const token_tags = ast.tokens.items(.tag);
104
105 for (members) |member_node| {
106 const name_token = switch (node_tags[member_node]) {
107 .global_var_decl,
108 .local_var_decl,
109 .simple_var_decl,
110 .aligned_var_decl,
111 => main_tokens[member_node] + 1,
112
113 .fn_proto_simple,
114 .fn_proto_multi,
115 .fn_proto_one,
116 .fn_proto,
117 .fn_decl,
118 => main_tokens[member_node] + 1,
119
120 else => continue,
121 };
122
123 assert(token_tags[name_token] == .identifier);
124 const name_bytes = ast.tokenSlice(name_token);
125
126 switch (action) {
127 .add => {
128 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
129
130 const gop = try w.in_scope_names.getOrPut(gpa, name_bytes);
131 if (!gop.found_existing) gop.value_ptr.* = 0;
132 gop.value_ptr.* += 1;
133 },
134 .remove => {
135 const entry = w.in_scope_names.getEntry(name_bytes).?;
136 if (entry.value_ptr.* <= 1) {
137 assert(w.in_scope_names.swapRemove(name_bytes));
138 } else {
139 entry.value_ptr.* -= 1;
140 }
141 },
142 }
143 }
144}
145
146fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
147 const ast = w.ast;
148 const datas = ast.nodes.items(.data);
149 switch (ast.nodes.items(.tag)[decl]) {
150 .fn_decl => {
151 const fn_proto = datas[decl].lhs;
152 try walkExpression(w, fn_proto);
153 const body_node = datas[decl].rhs;
154 if (!isFnBodyGutted(ast, body_node)) {
155 w.replace_names.clearRetainingCapacity();
156 try w.transformations.append(.{ .gut_function = decl });
157 try walkExpression(w, body_node);
158 }
159 },
160 .fn_proto_simple,
161 .fn_proto_multi,
162 .fn_proto_one,
163 .fn_proto,
164 => {
165 try walkExpression(w, decl);
166 },
167
168 .@"usingnamespace" => {
169 try w.transformations.append(.{ .delete_node = decl });
170 const expr = datas[decl].lhs;
171 try walkExpression(w, expr);
172 },
173
174 .global_var_decl,
175 .local_var_decl,
176 .simple_var_decl,
177 .aligned_var_decl,
178 => try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?),
179
180 .test_decl => {
181 try w.transformations.append(.{ .delete_node = decl });
182 try walkExpression(w, datas[decl].rhs);
183 },
184
185 .container_field_init,
186 .container_field_align,
187 .container_field,
188 => {
189 try w.transformations.append(.{ .delete_node = decl });
190 try walkContainerField(w, ast.fullContainerField(decl).?);
191 },
192
193 .@"comptime" => {
194 try w.transformations.append(.{ .delete_node = decl });
195 try walkExpression(w, decl);
196 },
197
198 .root => unreachable,
199 else => unreachable,
200 }
201}
202
203fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
204 const ast = w.ast;
205 const token_tags = ast.tokens.items(.tag);
206 const main_tokens = ast.nodes.items(.main_token);
207 const node_tags = ast.nodes.items(.tag);
208 const datas = ast.nodes.items(.data);
209 switch (node_tags[node]) {
210 .identifier => {
211 const name_ident = main_tokens[node];
212 assert(token_tags[name_ident] == .identifier);
213 const name_bytes = ast.tokenSlice(name_ident);
214 _ = w.unreferenced_globals.swapRemove(name_bytes);
215 if (w.replace_names.get(name_bytes)) |index| {
216 try w.transformations.items[index].delete_var_decl.references.append(w.arena, node);
217 }
218 },
219
220 .number_literal,
221 .char_literal,
222 .unreachable_literal,
223 .anyframe_literal,
224 .string_literal,
225 => {},
226
227 .multiline_string_literal => {},
228
229 .error_value => {},
230
231 .block_two,
232 .block_two_semicolon,
233 => {
234 const statements = [2]Ast.Node.Index{ datas[node].lhs, datas[node].rhs };
235 if (datas[node].lhs == 0) {
236 return walkBlock(w, node, statements[0..0]);
237 } else if (datas[node].rhs == 0) {
238 return walkBlock(w, node, statements[0..1]);
239 } else {
240 return walkBlock(w, node, statements[0..2]);
241 }
242 },
243 .block,
244 .block_semicolon,
245 => {
246 const statements = ast.extra_data[datas[node].lhs..datas[node].rhs];
247 return walkBlock(w, node, statements);
248 },
249
250 .@"errdefer" => {
251 const expr = datas[node].rhs;
252 return walkExpression(w, expr);
253 },
254
255 .@"defer" => {
256 const expr = datas[node].rhs;
257 return walkExpression(w, expr);
258 },
259 .@"comptime", .@"nosuspend" => {
260 const block = datas[node].lhs;
261 return walkExpression(w, block);
262 },
263
264 .@"suspend" => {
265 const body = datas[node].lhs;
266 return walkExpression(w, body);
267 },
268
269 .@"catch" => {
270 try walkExpression(w, datas[node].lhs); // target
271 try walkExpression(w, datas[node].rhs); // fallback
272 },
273
274 .field_access => {
275 const field_access = datas[node];
276 try walkExpression(w, field_access.lhs);
277 },
278
279 .error_union,
280 .switch_range,
281 => {
282 const infix = datas[node];
283 try walkExpression(w, infix.lhs);
284 return walkExpression(w, infix.rhs);
285 },
286 .for_range => {
287 const infix = datas[node];
288 try walkExpression(w, infix.lhs);
289 if (infix.rhs != 0) {
290 return walkExpression(w, infix.rhs);
291 }
292 },
293
294 .add,
295 .add_wrap,
296 .add_sat,
297 .array_cat,
298 .array_mult,
299 .assign,
300 .assign_bit_and,
301 .assign_bit_or,
302 .assign_shl,
303 .assign_shl_sat,
304 .assign_shr,
305 .assign_bit_xor,
306 .assign_div,
307 .assign_sub,
308 .assign_sub_wrap,
309 .assign_sub_sat,
310 .assign_mod,
311 .assign_add,
312 .assign_add_wrap,
313 .assign_add_sat,
314 .assign_mul,
315 .assign_mul_wrap,
316 .assign_mul_sat,
317 .bang_equal,
318 .bit_and,
319 .bit_or,
320 .shl,
321 .shl_sat,
322 .shr,
323 .bit_xor,
324 .bool_and,
325 .bool_or,
326 .div,
327 .equal_equal,
328 .greater_or_equal,
329 .greater_than,
330 .less_or_equal,
331 .less_than,
332 .merge_error_sets,
333 .mod,
334 .mul,
335 .mul_wrap,
336 .mul_sat,
337 .sub,
338 .sub_wrap,
339 .sub_sat,
340 .@"orelse",
341 => {
342 const infix = datas[node];
343 try walkExpression(w, infix.lhs);
344 try walkExpression(w, infix.rhs);
345 },
346
347 .assign_destructure => {
348 const lhs_count = ast.extra_data[datas[node].lhs];
349 assert(lhs_count > 1);
350 const lhs_exprs = ast.extra_data[datas[node].lhs + 1 ..][0..lhs_count];
351 const rhs = datas[node].rhs;
352
353 for (lhs_exprs) |lhs_node| {
354 switch (node_tags[lhs_node]) {
355 .global_var_decl,
356 .local_var_decl,
357 .simple_var_decl,
358 .aligned_var_decl,
359 => try walkLocalVarDecl(w, ast.fullVarDecl(lhs_node).?),
360
361 else => try walkExpression(w, lhs_node),
362 }
363 }
364 return walkExpression(w, rhs);
365 },
366
367 .bit_not,
368 .bool_not,
369 .negation,
370 .negation_wrap,
371 .optional_type,
372 .address_of,
373 => {
374 return walkExpression(w, datas[node].lhs);
375 },
376
377 .@"try",
378 .@"resume",
379 .@"await",
380 => {
381 return walkExpression(w, datas[node].lhs);
382 },
383
384 .array_type,
385 .array_type_sentinel,
386 => {},
387
388 .ptr_type_aligned,
389 .ptr_type_sentinel,
390 .ptr_type,
391 .ptr_type_bit_range,
392 => {},
393
394 .array_init_one,
395 .array_init_one_comma,
396 .array_init_dot_two,
397 .array_init_dot_two_comma,
398 .array_init_dot,
399 .array_init_dot_comma,
400 .array_init,
401 .array_init_comma,
402 => {
403 var elements: [2]Ast.Node.Index = undefined;
404 return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
405 },
406
407 .struct_init_one,
408 .struct_init_one_comma,
409 .struct_init_dot_two,
410 .struct_init_dot_two_comma,
411 .struct_init_dot,
412 .struct_init_dot_comma,
413 .struct_init,
414 .struct_init_comma,
415 => {
416 var buf: [2]Ast.Node.Index = undefined;
417 return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
418 },
419
420 .call_one,
421 .call_one_comma,
422 .async_call_one,
423 .async_call_one_comma,
424 .call,
425 .call_comma,
426 .async_call,
427 .async_call_comma,
428 => {
429 var buf: [1]Ast.Node.Index = undefined;
430 return walkCall(w, ast.fullCall(&buf, node).?);
431 },
432
433 .array_access => {
434 const suffix = datas[node];
435 try walkExpression(w, suffix.lhs);
436 try walkExpression(w, suffix.rhs);
437 },
438
439 .slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
440
441 .deref => {
442 try walkExpression(w, datas[node].lhs);
443 },
444
445 .unwrap_optional => {
446 try walkExpression(w, datas[node].lhs);
447 },
448
449 .@"break" => {
450 const label_token = datas[node].lhs;
451 const target = datas[node].rhs;
452 if (label_token == 0 and target == 0) {
453 // no expressions
454 } else if (label_token == 0 and target != 0) {
455 try walkExpression(w, target);
456 } else if (label_token != 0 and target == 0) {
457 try walkIdentifier(w, label_token);
458 } else if (label_token != 0 and target != 0) {
459 try walkExpression(w, target);
460 }
461 },
462
463 .@"continue" => {
464 const label = datas[node].lhs;
465 if (label != 0) {
466 return walkIdentifier(w, label); // label
467 }
468 },
469
470 .@"return" => {
471 if (datas[node].lhs != 0) {
472 try walkExpression(w, datas[node].lhs);
473 }
474 },
475
476 .grouped_expression => {
477 try walkExpression(w, datas[node].lhs);
478 },
479
480 .container_decl,
481 .container_decl_trailing,
482 .container_decl_arg,
483 .container_decl_arg_trailing,
484 .container_decl_two,
485 .container_decl_two_trailing,
486 .tagged_union,
487 .tagged_union_trailing,
488 .tagged_union_enum_tag,
489 .tagged_union_enum_tag_trailing,
490 .tagged_union_two,
491 .tagged_union_two_trailing,
492 => {
493 var buf: [2]Ast.Node.Index = undefined;
494 return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
495 },
496
497 .error_set_decl => {
498 const error_token = main_tokens[node];
499 const lbrace = error_token + 1;
500 const rbrace = datas[node].rhs;
501
502 var i = lbrace + 1;
503 while (i < rbrace) : (i += 1) {
504 switch (token_tags[i]) {
505 .doc_comment => unreachable, // TODO
506 .identifier => try walkIdentifier(w, i),
507 .comma => {},
508 else => unreachable,
509 }
510 }
511 },
512
513 .builtin_call_two, .builtin_call_two_comma => {
514 if (datas[node].lhs == 0) {
515 return walkBuiltinCall(w, node, &.{});
516 } else if (datas[node].rhs == 0) {
517 return walkBuiltinCall(w, node, &.{datas[node].lhs});
518 } else {
519 return walkBuiltinCall(w, node, &.{ datas[node].lhs, datas[node].rhs });
520 }
521 },
522 .builtin_call, .builtin_call_comma => {
523 const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
524 return walkBuiltinCall(w, node, params);
525 },
526
527 .fn_proto_simple,
528 .fn_proto_multi,
529 .fn_proto_one,
530 .fn_proto,
531 => {
532 var buf: [1]Ast.Node.Index = undefined;
533 return walkFnProto(w, ast.fullFnProto(&buf, node).?);
534 },
535
536 .anyframe_type => {
537 if (datas[node].rhs != 0) {
538 return walkExpression(w, datas[node].rhs);
539 }
540 },
541
542 .@"switch",
543 .switch_comma,
544 => {
545 const condition = datas[node].lhs;
546 const extra = ast.extraData(datas[node].rhs, Ast.Node.SubRange);
547 const cases = ast.extra_data[extra.start..extra.end];
548
549 try walkExpression(w, condition); // condition expression
550 try walkExpressions(w, cases);
551 },
552
553 .switch_case_one,
554 .switch_case_inline_one,
555 .switch_case,
556 .switch_case_inline,
557 => return walkSwitchCase(w, ast.fullSwitchCase(node).?),
558
559 .while_simple,
560 .while_cont,
561 .@"while",
562 => return walkWhile(w, node, ast.fullWhile(node).?),
563
564 .for_simple,
565 .@"for",
566 => return walkFor(w, ast.fullFor(node).?),
567
568 .if_simple,
569 .@"if",
570 => return walkIf(w, node, ast.fullIf(node).?),
571
572 .asm_simple,
573 .@"asm",
574 => return walkAsm(w, ast.fullAsm(node).?),
575
576 .enum_literal => {
577 return walkIdentifier(w, main_tokens[node]); // name
578 },
579
580 .fn_decl => unreachable,
581 .container_field => unreachable,
582 .container_field_init => unreachable,
583 .container_field_align => unreachable,
584 .root => unreachable,
585 .global_var_decl => unreachable,
586 .local_var_decl => unreachable,
587 .simple_var_decl => unreachable,
588 .aligned_var_decl => unreachable,
589 .@"usingnamespace" => unreachable,
590 .test_decl => unreachable,
591 .asm_output => unreachable,
592 .asm_input => unreachable,
593 }
594}
595
596fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void {
597 _ = decl_node;
598
599 if (var_decl.ast.type_node != 0) {
600 try walkExpression(w, var_decl.ast.type_node);
601 }
602
603 if (var_decl.ast.align_node != 0) {
604 try walkExpression(w, var_decl.ast.align_node);
605 }
606
607 if (var_decl.ast.addrspace_node != 0) {
608 try walkExpression(w, var_decl.ast.addrspace_node);
609 }
610
611 if (var_decl.ast.section_node != 0) {
612 try walkExpression(w, var_decl.ast.section_node);
613 }
614
615 if (var_decl.ast.init_node != 0) {
616 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
617 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
618 }
619 try walkExpression(w, var_decl.ast.init_node);
620 }
621}
622
623fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
624 try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name
625
626 if (var_decl.ast.type_node != 0) {
627 try walkExpression(w, var_decl.ast.type_node);
628 }
629
630 if (var_decl.ast.align_node != 0) {
631 try walkExpression(w, var_decl.ast.align_node);
632 }
633
634 if (var_decl.ast.addrspace_node != 0) {
635 try walkExpression(w, var_decl.ast.addrspace_node);
636 }
637
638 if (var_decl.ast.section_node != 0) {
639 try walkExpression(w, var_decl.ast.section_node);
640 }
641
642 if (var_decl.ast.init_node != 0) {
643 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
644 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
645 }
646 try walkExpression(w, var_decl.ast.init_node);
647 }
648}
649
650fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
651 if (field.ast.type_expr != 0) {
652 try walkExpression(w, field.ast.type_expr); // type
653 }
654 if (field.ast.align_expr != 0) {
655 try walkExpression(w, field.ast.align_expr); // alignment
656 }
657 if (field.ast.value_expr != 0) {
658 try walkExpression(w, field.ast.value_expr); // value
659 }
660}
661
662fn walkBlock(
663 w: *Walk,
664 block_node: Ast.Node.Index,
665 statements: []const Ast.Node.Index,
666) Error!void {
667 _ = block_node;
668 const ast = w.ast;
669 const node_tags = ast.nodes.items(.tag);
670
671 for (statements) |stmt| {
672 switch (node_tags[stmt]) {
673 .global_var_decl,
674 .local_var_decl,
675 .simple_var_decl,
676 .aligned_var_decl,
677 => {
678 const var_decl = ast.fullVarDecl(stmt).?;
679 if (var_decl.ast.init_node != 0 and
680 isUndefinedIdent(w.ast, var_decl.ast.init_node))
681 {
682 try w.transformations.append(.{ .delete_var_decl = .{
683 .var_decl_node = stmt,
684 .references = .{},
685 } });
686 const name_tok = var_decl.ast.mut_token + 1;
687 const name_bytes = ast.tokenSlice(name_tok);
688 try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1));
689 } else {
690 try walkLocalVarDecl(w, var_decl);
691 }
692 },
693
694 else => {
695 switch (categorizeStmt(ast, stmt)) {
696 // Don't try to remove `_ = foo;` discards; those are handled separately.
697 .discard_identifier => {},
698 // definitely try to remove `_ = undefined;` though.
699 .discard_undefined, .trap_call, .other => {
700 try w.transformations.append(.{ .delete_node = stmt });
701 },
702 }
703 try walkExpression(w, stmt);
704 },
705 }
706 }
707}
708
709fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
710 try walkExpression(w, array_type.ast.elem_count);
711 if (array_type.ast.sentinel != 0) {
712 try walkExpression(w, array_type.ast.sentinel);
713 }
714 return walkExpression(w, array_type.ast.elem_type);
715}
716
717fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
718 if (array_init.ast.type_expr != 0) {
719 try walkExpression(w, array_init.ast.type_expr); // T
720 }
721 for (array_init.ast.elements) |elem_init| {
722 try walkExpression(w, elem_init);
723 }
724}
725
726fn walkStructInit(
727 w: *Walk,
728 struct_node: Ast.Node.Index,
729 struct_init: Ast.full.StructInit,
730) Error!void {
731 _ = struct_node;
732 if (struct_init.ast.type_expr != 0) {
733 try walkExpression(w, struct_init.ast.type_expr); // T
734 }
735 for (struct_init.ast.fields) |field_init| {
736 try walkExpression(w, field_init);
737 }
738}
739
740fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
741 try walkExpression(w, call.ast.fn_expr);
742 try walkParamList(w, call.ast.params);
743}
744
745fn walkSlice(
746 w: *Walk,
747 slice_node: Ast.Node.Index,
748 slice: Ast.full.Slice,
749) Error!void {
750 _ = slice_node;
751 try walkExpression(w, slice.ast.sliced);
752 try walkExpression(w, slice.ast.start);
753 if (slice.ast.end != 0) {
754 try walkExpression(w, slice.ast.end);
755 }
756 if (slice.ast.sentinel != 0) {
757 try walkExpression(w, slice.ast.sentinel);
758 }
759}
760
761fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
762 const ast = w.ast;
763 const token_tags = ast.tokens.items(.tag);
764 assert(token_tags[name_ident] == .identifier);
765 const name_bytes = ast.tokenSlice(name_ident);
766 _ = w.unreferenced_globals.swapRemove(name_bytes);
767}
768
769fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
770 _ = w;
771 _ = name_ident;
772}
773
774fn walkContainerDecl(
775 w: *Walk,
776 container_decl_node: Ast.Node.Index,
777 container_decl: Ast.full.ContainerDecl,
778) Error!void {
779 _ = container_decl_node;
780 if (container_decl.ast.arg != 0) {
781 try walkExpression(w, container_decl.ast.arg);
782 }
783 try walkMembers(w, container_decl.ast.members);
784}
785
786fn walkBuiltinCall(
787 w: *Walk,
788 call_node: Ast.Node.Index,
789 params: []const Ast.Node.Index,
790) Error!void {
791 const ast = w.ast;
792 const main_tokens = ast.nodes.items(.main_token);
793 const builtin_token = main_tokens[call_node];
794 const builtin_name = ast.tokenSlice(builtin_token);
795 const info = BuiltinFn.list.get(builtin_name).?;
796 switch (info.tag) {
797 .import => {
798 const operand_node = params[0];
799 const str_lit_token = main_tokens[operand_node];
800 const token_bytes = ast.tokenSlice(str_lit_token);
801 if (std.mem.endsWith(u8, token_bytes, ".zig\"")) {
802 const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch
803 unreachable;
804 try w.transformations.append(.{ .inline_imported_file = .{
805 .builtin_call_node = call_node,
806 .imported_string = imported_string,
807 .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init(
808 w.arena,
809 w.in_scope_names.keys(),
810 &.{},
811 ),
812 } });
813 }
814 },
815 else => {},
816 }
817 for (params) |param_node| {
818 try walkExpression(w, param_node);
819 }
820}
821
822fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
823 const ast = w.ast;
824
825 {
826 var it = fn_proto.iterate(ast);
827 while (it.next()) |param| {
828 if (param.type_expr != 0) {
829 try walkExpression(w, param.type_expr);
830 }
831 }
832 }
833
834 if (fn_proto.ast.align_expr != 0) {
835 try walkExpression(w, fn_proto.ast.align_expr);
836 }
837
838 if (fn_proto.ast.addrspace_expr != 0) {
839 try walkExpression(w, fn_proto.ast.addrspace_expr);
840 }
841
842 if (fn_proto.ast.section_expr != 0) {
843 try walkExpression(w, fn_proto.ast.section_expr);
844 }
845
846 if (fn_proto.ast.callconv_expr != 0) {
847 try walkExpression(w, fn_proto.ast.callconv_expr);
848 }
849
850 try walkExpression(w, fn_proto.ast.return_type);
851}
852
853fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
854 for (expressions) |expression| {
855 try walkExpression(w, expression);
856 }
857}
858
859fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
860 for (switch_case.ast.values) |value_expr| {
861 try walkExpression(w, value_expr);
862 }
863 try walkExpression(w, switch_case.ast.target_expr);
864}
865
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
897 try walkExpression(w, while_node.ast.cond_expr); // condition
898
899 if (while_node.ast.cont_expr != 0) {
900 try walkExpression(w, while_node.ast.cont_expr);
901 }
902
903 if (while_node.ast.then_expr != 0) {
904 try walkExpression(w, while_node.ast.then_expr);
905 }
906 if (while_node.ast.else_expr != 0) {
907 try walkExpression(w, while_node.ast.else_expr);
908 }
909}
910
911fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
912 try walkParamList(w, for_node.ast.inputs);
913 if (for_node.ast.then_expr != 0) {
914 try walkExpression(w, for_node.ast.then_expr);
915 }
916 if (for_node.ast.else_expr != 0) {
917 try walkExpression(w, for_node.ast.else_expr);
918 }
919}
920
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
952 try walkExpression(w, if_node.ast.cond_expr); // condition
953
954 if (if_node.ast.then_expr != 0) {
955 try walkExpression(w, if_node.ast.then_expr);
956 }
957 if (if_node.ast.else_expr != 0) {
958 try walkExpression(w, if_node.ast.else_expr);
959 }
960}
961
962fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
963 try walkExpression(w, asm_node.ast.template);
964 for (asm_node.ast.items) |item| {
965 try walkExpression(w, item);
966 }
967}
968
969fn walkParamList(w: *Walk, params: []const Ast.Node.Index) Error!void {
970 for (params) |param_node| {
971 try walkExpression(w, param_node);
972 }
973}
974
975/// Check if it is already gutted (i.e. its body replaced with `@trap()`).
976fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
977 // skip over discards
978 const node_tags = ast.nodes.items(.tag);
979 const datas = ast.nodes.items(.data);
980 var statements_buf: [2]Ast.Node.Index = undefined;
981 const statements = switch (node_tags[body_node]) {
982 .block_two,
983 .block_two_semicolon,
984 => blk: {
985 statements_buf[0..2].* = .{ datas[body_node].lhs, datas[body_node].rhs };
986 break :blk if (datas[body_node].lhs == 0)
987 statements_buf[0..0]
988 else if (datas[body_node].rhs == 0)
989 statements_buf[0..1]
990 else
991 statements_buf[0..2];
992 },
993
994 .block,
995 .block_semicolon,
996 => ast.extra_data[datas[body_node].lhs..datas[body_node].rhs],
997
998 else => return false,
999 };
1000 var i: usize = 0;
1001 while (i < statements.len) : (i += 1) {
1002 switch (categorizeStmt(ast, statements[i])) {
1003 .discard_identifier => continue,
1004 .trap_call => return i + 1 == statements.len,
1005 else => return false,
1006 }
1007 }
1008 return false;
1009}
1010
1011const StmtCategory = enum {
1012 discard_undefined,
1013 discard_identifier,
1014 trap_call,
1015 other,
1016};
1017
1018fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
1019 const node_tags = ast.nodes.items(.tag);
1020 const datas = ast.nodes.items(.data);
1021 const main_tokens = ast.nodes.items(.main_token);
1022 switch (node_tags[stmt]) {
1023 .builtin_call_two, .builtin_call_two_comma => {
1024 if (datas[stmt].lhs == 0) {
1025 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{});
1026 } else if (datas[stmt].rhs == 0) {
1027 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{datas[stmt].lhs});
1028 } else {
1029 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{ datas[stmt].lhs, datas[stmt].rhs });
1030 }
1031 },
1032 .builtin_call, .builtin_call_comma => {
1033 const params = ast.extra_data[datas[stmt].lhs..datas[stmt].rhs];
1034 return categorizeBuiltinCall(ast, main_tokens[stmt], params);
1035 },
1036 .assign => {
1037 const infix = datas[stmt];
1038 if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier) {
1039 const name_bytes = ast.tokenSlice(main_tokens[infix.rhs]);
1040 if (std.mem.eql(u8, name_bytes, "undefined")) {
1041 return .discard_undefined;
1042 } else {
1043 return .discard_identifier;
1044 }
1045 }
1046 return .other;
1047 },
1048 else => return .other,
1049 }
1050}
1051
1052fn categorizeBuiltinCall(
1053 ast: *const Ast,
1054 builtin_token: Ast.TokenIndex,
1055 params: []const Ast.Node.Index,
1056) StmtCategory {
1057 if (params.len != 0) return .other;
1058 const name_bytes = ast.tokenSlice(builtin_token);
1059 if (std.mem.eql(u8, name_bytes, "@trap"))
1060 return .trap_call;
1061 return .other;
1062}
1063
1064fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1065 return isMatchingIdent(ast, node, "_");
1066}
1067
1068fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1069 return isMatchingIdent(ast, node, "undefined");
1070}
1071
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
1080fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
1081 const node_tags = ast.nodes.items(.tag);
1082 const main_tokens = ast.nodes.items(.main_token);
1083 switch (node_tags[node]) {
1084 .identifier => {
1085 const token_index = main_tokens[node];
1086 const name_bytes = ast.tokenSlice(token_index);
1087 return std.mem.eql(u8, name_bytes, string);
1088 },
1089 else => return false,
1090 }
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}
stage1/config.zig.in-1
......@@ -13,4 +13,3 @@ pub const skip_non_native = false;
1313pub const only_c = false;
1414pub const force_gpa = false;
1515pub const only_core_functionality = true;
16pub const only_reduce = false;