authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-05 03:39:01-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-11-05 03:39:01-05:00
logdc63426b1eaef9c65152c8e052e5552e593e9382
tree4baa141bdd1da68a3f15164988317350f95b76c7
parentf24ceec35a6fd1e5e6a671461b78919b5f588a32
parenta9002156a09130038abcf418609fea725ce71bc2
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17866 from ziglang/reduce-inline-import

zig reduce: support inlining `@import` and more

4 files changed, 433 insertions(+), 79 deletions(-)

lib/std/array_hash_map.zig+13
......@@ -574,6 +574,19 @@ pub fn ArrayHashMapUnmanaged(
574574 };
575575 }
576576
577 pub fn init(allocator: Allocator, key_list: []const K, value_list: []const V) !Self {
578 var self: Self = .{};
579 try self.entries.resize(allocator, key_list.len);
580 errdefer self.entries.deinit(allocator);
581 @memcpy(self.keys(), key_list);
582 if (@sizeOf(V) != 0) {
583 assert(key_list.len == value_list.len);
584 @memcpy(self.values(), value_list);
585 }
586 try self.reIndex(allocator);
587 return self;
588 }
589
577590 /// Frees the backing allocation and leaves the map in an undefined state.
578591 /// Note that this does not free keys or values. You must take care of that
579592 /// before calling this function, if it is needed.
lib/std/zig/render.zig+114-20
......@@ -24,14 +24,22 @@ pub const Fixups = struct {
2424 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
2525 /// These global declarations will be omitted.
2626 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
27 /// These expressions will be replaced with `undefined`.
28 replace_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .{},
27 /// These expressions will be replaced with the string value.
28 replace_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .{},
29 /// Change all identifier names matching the key to be value instead.
30 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .{},
31
32 /// All `@import` builtin calls which refer to a file path will be prefixed
33 /// with this path.
34 rebase_imported_paths: ?[]const u8 = null,
2935
3036 pub fn count(f: Fixups) usize {
3137 return f.unused_var_decls.count() +
3238 f.gut_functions.count() +
3339 f.omit_nodes.count() +
34 f.replace_nodes.count();
40 f.replace_nodes.count() +
41 f.rename_identifiers.count() +
42 @intFromBool(f.rebase_imported_paths != null);
3543 }
3644
3745 pub fn clearRetainingCapacity(f: *Fixups) void {
......@@ -39,6 +47,9 @@ pub const Fixups = struct {
3947 f.gut_functions.clearRetainingCapacity();
4048 f.omit_nodes.clearRetainingCapacity();
4149 f.replace_nodes.clearRetainingCapacity();
50 f.rename_identifiers.clearRetainingCapacity();
51
52 f.rebase_imported_paths = null;
4253 }
4354
4455 pub fn deinit(f: *Fixups, gpa: Allocator) void {
......@@ -46,6 +57,7 @@ pub const Fixups = struct {
4657 f.gut_functions.deinit(gpa);
4758 f.omit_nodes.deinit(gpa);
4859 f.replace_nodes.deinit(gpa);
60 f.rename_identifiers.deinit(gpa);
4961 f.* = undefined;
5062 }
5163};
......@@ -121,6 +133,7 @@ fn renderMember(
121133) Error!void {
122134 const tree = r.tree;
123135 const ais = r.ais;
136 const node_tags = tree.nodes.items(.tag);
124137 const token_tags = tree.tokens.items(.tag);
125138 const main_tokens = tree.nodes.items(.main_token);
126139 const datas = tree.nodes.items(.data);
......@@ -182,6 +195,45 @@ fn renderMember(
182195 ais.popIndent();
183196 try ais.insertNewline();
184197 try renderToken(r, tree.lastToken(body_node), space); // rbrace
198 } else if (r.fixups.unused_var_decls.count() != 0) {
199 ais.pushIndentNextLine();
200 const lbrace = tree.nodes.items(.main_token)[body_node];
201 try renderToken(r, lbrace, .newline);
202
203 var fn_proto_buf: [1]Ast.Node.Index = undefined;
204 const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?;
205 var it = full_fn_proto.iterate(&tree);
206 while (it.next()) |param| {
207 const name_ident = param.name_token.?;
208 assert(token_tags[name_ident] == .identifier);
209 if (r.fixups.unused_var_decls.contains(name_ident)) {
210 const w = ais.writer();
211 try w.writeAll("_ = ");
212 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
213 try w.writeAll(";\n");
214 }
215 }
216 var statements_buf: [2]Ast.Node.Index = undefined;
217 const statements = switch (node_tags[body_node]) {
218 .block_two,
219 .block_two_semicolon,
220 => b: {
221 statements_buf = .{ datas[body_node].lhs, datas[body_node].rhs };
222 if (datas[body_node].lhs == 0) {
223 break :b statements_buf[0..0];
224 } else if (datas[body_node].rhs == 0) {
225 break :b statements_buf[0..1];
226 } else {
227 break :b statements_buf[0..2];
228 }
229 },
230 .block,
231 .block_semicolon,
232 => tree.extra_data[datas[body_node].lhs..datas[body_node].rhs],
233
234 else => unreachable,
235 };
236 return finishRenderBlock(r, body_node, statements, space);
185237 } else {
186238 return renderExpression(r, body_node, space);
187239 }
......@@ -277,8 +329,8 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
277329 const main_tokens = tree.nodes.items(.main_token);
278330 const node_tags = tree.nodes.items(.tag);
279331 const datas = tree.nodes.items(.data);
280 if (r.fixups.replace_nodes.contains(node)) {
281 try ais.writer().writeAll("undefined");
332 if (r.fixups.replace_nodes.get(node)) |replacement| {
333 try ais.writer().writeAll(replacement);
282334 try renderOnlySpace(r, space);
283335 return;
284336 }
......@@ -1057,7 +1109,7 @@ fn renderVarDecl(
10571109 space: Space,
10581110) Error!void {
10591111 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1060 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token)) {
1112 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) {
10611113 // Discard the variable like this: `_ = foo;`
10621114 const w = r.ais.writer();
10631115 try w.writeAll("_ = ");
......@@ -1515,6 +1567,7 @@ fn renderBuiltinCall(
15151567 const tree = r.tree;
15161568 const ais = r.ais;
15171569 const token_tags = tree.tokens.items(.tag);
1570 const main_tokens = tree.nodes.items(.main_token);
15181571
15191572 // TODO remove before release of 0.12.0
15201573 const slice = tree.tokenSlice(builtin_token);
......@@ -1609,6 +1662,26 @@ fn renderBuiltinCall(
16091662 return renderToken(r, builtin_token + 2, space); // )
16101663 }
16111664
1665 if (r.fixups.rebase_imported_paths) |prefix| {
1666 if (mem.eql(u8, slice, "@import")) f: {
1667 const param = params[0];
1668 const str_lit_token = main_tokens[param];
1669 assert(token_tags[str_lit_token] == .string_literal);
1670 const token_bytes = tree.tokenSlice(str_lit_token);
1671 const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
1672 error.OutOfMemory => return error.OutOfMemory,
1673 error.InvalidLiteral => break :f,
1674 };
1675 defer r.gpa.free(imported_string);
1676 const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
1677 defer r.gpa.free(new_string);
1678
1679 try renderToken(r, builtin_token + 1, .none); // (
1680 try ais.writer().print("\"{}\"", .{std.zig.fmtEscapes(new_string)});
1681 return renderToken(r, str_lit_token + 1, space); // )
1682 }
1683 }
1684
16121685 const last_param = params[params.len - 1];
16131686 const after_last_param_token = tree.lastToken(last_param) + 1;
16141687
......@@ -1934,7 +2007,6 @@ fn renderBlock(
19342007 const tree = r.tree;
19352008 const ais = r.ais;
19362009 const token_tags = tree.tokens.items(.tag);
1937 const node_tags = tree.nodes.items(.tag);
19382010 const lbrace = tree.nodes.items(.main_token)[block_node];
19392011
19402012 if (token_tags[lbrace - 1] == .colon and
......@@ -1943,22 +2015,37 @@ fn renderBlock(
19432015 try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier
19442016 try renderToken(r, lbrace - 1, .space); // :
19452017 }
1946
19472018 ais.pushIndentNextLine();
19482019 if (statements.len == 0) {
19492020 try renderToken(r, lbrace, .none);
1950 } else {
1951 try renderToken(r, lbrace, .newline);
1952 for (statements, 0..) |stmt, i| {
1953 if (i != 0) try renderExtraNewline(r, stmt);
1954 switch (node_tags[stmt]) {
1955 .global_var_decl,
1956 .local_var_decl,
1957 .simple_var_decl,
1958 .aligned_var_decl,
1959 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
1960 else => try renderExpression(r, stmt, .semicolon),
1961 }
2021 ais.popIndent();
2022 try renderToken(r, tree.lastToken(block_node), space); // rbrace
2023 return;
2024 }
2025 try renderToken(r, lbrace, .newline);
2026 return finishRenderBlock(r, block_node, statements, space);
2027}
2028
2029fn finishRenderBlock(
2030 r: *Render,
2031 block_node: Ast.Node.Index,
2032 statements: []const Ast.Node.Index,
2033 space: Space,
2034) Error!void {
2035 const tree = r.tree;
2036 const node_tags = tree.nodes.items(.tag);
2037 const ais = r.ais;
2038 for (statements, 0..) |stmt, i| {
2039 if (i != 0) try renderExtraNewline(r, stmt);
2040 if (r.fixups.omit_nodes.contains(stmt)) continue;
2041 switch (node_tags[stmt]) {
2042 .global_var_decl,
2043 .local_var_decl,
2044 .simple_var_decl,
2045 .aligned_var_decl,
2046 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
2047
2048 else => try renderExpression(r, stmt, .semicolon),
19622049 }
19632050 }
19642051 ais.popIndent();
......@@ -2809,6 +2896,13 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote
28092896 const token_tags = tree.tokens.items(.tag);
28102897 assert(token_tags[token_index] == .identifier);
28112898 const lexeme = tokenSliceForRender(tree, token_index);
2899
2900 if (r.fixups.rename_identifiers.get(lexeme)) |mangled| {
2901 try r.ais.writer().writeAll(mangled);
2902 try renderSpace(r, token_index, lexeme.len, space);
2903 return;
2904 }
2905
28122906 if (lexeme[0] != '@') {
28132907 return renderToken(r, token_index, space);
28142908 }
src/reduce.zig+144-20
......@@ -5,6 +5,8 @@ const assert = std.debug.assert;
55const fatal = @import("./main.zig").fatal;
66const Ast = std.zig.Ast;
77const Walk = @import("reduce/Walk.zig");
8const AstGen = @import("AstGen.zig");
9const Zir = @import("Zir.zig");
810
911const usage =
1012 \\zig reduce [options] ./checker root_source_file.zig [-- [argv]]
......@@ -39,8 +41,6 @@ const Interestingness = enum { interesting, unknown, boring };
3941// - add support for parsing the module flags
4042// - more fancy transformations
4143// - @import inlining of modules
42// - @import inlining of files
43// - deleting unused functions and other globals
4444// - removing statements or blocks of code
4545// - replacing operands of `and` and `or` with `true` and `false`
4646// - replacing if conditions with `true` and `false`
......@@ -109,8 +109,14 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
109109 var rendered = std.ArrayList(u8).init(gpa);
110110 defer rendered.deinit();
111111
112 var tree = try parse(gpa, arena, root_source_file_path);
113 defer tree.deinit(gpa);
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 }
114120
115121 if (!skip_smoke_test) {
116122 std.debug.print("smoke testing the interestingness check...\n", .{});
......@@ -126,6 +132,10 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
126132
127133 var fixups: Ast.Fixups = .{};
128134 defer fixups.deinit(gpa);
135
136 var more_fixups: Ast.Fixups = .{};
137 defer more_fixups.deinit(gpa);
138
129139 var rng = std.rand.DefaultPrng.init(seed);
130140
131141 // 1. Walk the AST of the source file looking for independent
......@@ -145,7 +155,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
145155
146156 var transformations = std.ArrayList(Walk.Transformation).init(gpa);
147157 defer transformations.deinit();
148 try Walk.findTransformations(&tree, &transformations);
158 try Walk.findTransformations(arena, &tree, &transformations);
149159 sortTransformations(transformations.items, rng.random());
150160
151161 fresh: while (transformations.items.len > 0) {
......@@ -156,29 +166,80 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
156166 var start_index: usize = 0;
157167
158168 while (start_index < transformations.items.len) {
159 subset_size = @max(1, subset_size / 2);
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;
160173
161174 const this_set = transformations.items[start_index..][0..subset_size];
162 try transformationsToFixups(gpa, this_set, &fixups);
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);
163181
164182 rendered.clearRetainingCapacity();
165183 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
166228 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
229 //std.debug.print("trying this code:\n{s}\n", .{rendered.items});
167230
168231 const interestingness = try runCheck(arena, interestingness_argv.items);
169 std.debug.print("{d} random transformations: {s}. {d} remaining\n", .{
170 subset_size, @tagName(interestingness), transformations.items.len - start_index,
232 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
233 subset_size, @tagName(interestingness), start_index, transformations.items.len,
171234 });
172235 switch (interestingness) {
173236 .interesting => {
174 const new_tree = try parse(gpa, arena, root_source_file_path);
237 const new_tree = try parse(gpa, root_source_file_path);
238 gpa.free(tree.source);
175239 tree.deinit(gpa);
176240 tree = new_tree;
177241
178 try Walk.findTransformations(&tree, &transformations);
179 // Resetting based on the seed again means we will get the same
180 // results if restarting the reduction process from this new point.
181 rng = std.rand.DefaultPrng.init(seed);
242 try Walk.findTransformations(arena, &tree, &transformations);
182243 sortTransformations(transformations.items, rng.random());
183244
184245 continue :fresh;
......@@ -188,6 +249,11 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
188249 // If we tested only one transformation, move on to the next one.
189250 if (subset_size == 1) {
190251 start_index += 1;
252 } else {
253 start_index += subset_size;
254 if (start_index + subset_size > transformations.items.len) {
255 start_index = 0;
256 }
191257 }
192258 },
193259 }
......@@ -241,6 +307,8 @@ fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness
241307
242308fn transformationsToFixups(
243309 gpa: Allocator,
310 arena: Allocator,
311 root_source_file_path: []const u8,
244312 transforms: []const Walk.Transformation,
245313 fixups: *Ast.Fixups,
246314) !void {
......@@ -253,21 +321,77 @@ fn transformationsToFixups(
253321 .delete_node => |decl_node| {
254322 try fixups.omit_nodes.put(gpa, decl_node, {});
255323 },
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.put(gpa, ident_node, "undefined");
328 }
329 },
256330 .replace_with_undef => |node| {
257 try fixups.replace_nodes.put(gpa, node, {});
331 try fixups.replace_nodes.put(gpa, node, "undefined");
332 },
333 .inline_imported_file => |inline_imported_file| {
334 const full_imported_path = try std.fs.path.join(gpa, &.{
335 std.fs.path.dirname(root_source_file_path) orelse ".",
336 inline_imported_file.imported_string,
337 });
338 defer gpa.free(full_imported_path);
339 var other_file_ast = try parse(gpa, full_imported_path);
340 defer {
341 gpa.free(other_file_ast.source);
342 other_file_ast.deinit(gpa);
343 }
344
345 var inlined_fixups: Ast.Fixups = .{};
346 defer inlined_fixups.deinit(gpa);
347 if (std.fs.path.dirname(inline_imported_file.imported_string)) |dirname| {
348 inlined_fixups.rebase_imported_paths = dirname;
349 }
350 for (inline_imported_file.in_scope_names.keys()) |name| {
351 // This name needs to be mangled in order to not cause an
352 // ambiguous reference error.
353 var i: u32 = 2;
354 const mangled = while (true) : (i += 1) {
355 const mangled = try std.fmt.allocPrint(gpa, "{s}{d}", .{ name, i });
356 if (!inline_imported_file.in_scope_names.contains(mangled))
357 break mangled;
358 gpa.free(mangled);
359 };
360 try inlined_fixups.rename_identifiers.put(gpa, name, mangled);
361 }
362 defer {
363 for (inlined_fixups.rename_identifiers.values()) |v| {
364 gpa.free(v);
365 }
366 }
367
368 var other_source = std.ArrayList(u8).init(gpa);
369 defer other_source.deinit();
370 try other_source.appendSlice("struct {\n");
371 try other_file_ast.renderToArrayList(&other_source, inlined_fixups);
372 try other_source.appendSlice("}");
373
374 try fixups.replace_nodes.put(
375 gpa,
376 inline_imported_file.builtin_call_node,
377 try arena.dupe(u8, other_source.items),
378 );
258379 },
259380 };
260381}
261382
262fn parse(gpa: Allocator, arena: Allocator, root_source_file_path: []const u8) !Ast {
263 const source_code = try std.fs.cwd().readFileAllocOptions(
264 arena,
265 root_source_file_path,
383fn parse(gpa: Allocator, file_path: []const u8) !Ast {
384 const source_code = std.fs.cwd().readFileAllocOptions(
385 gpa,
386 file_path,
266387 std.math.maxInt(u32),
267388 null,
268389 1,
269390 0,
270 );
391 ) catch |err| {
392 fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) });
393 };
394 errdefer gpa.free(source_code);
271395
272396 var tree = try Ast.parse(gpa, source_code, .zig);
273397 errdefer tree.deinit(gpa);
src/reduce/Walk.zig+162-39
......@@ -2,11 +2,15 @@ const std = @import("std");
22const Ast = std.zig.Ast;
33const Walk = @This();
44const assert = std.debug.assert;
5const BuiltinFn = @import("../BuiltinFn.zig");
56
67ast: *const Ast,
78transformations: *std.ArrayList(Transformation),
89unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
10in_scope_names: std.StringArrayHashMapUnmanaged(u32),
11replace_names: std.StringArrayHashMapUnmanaged(u32),
912gpa: std.mem.Allocator,
13arena: std.mem.Allocator,
1014
1115pub const Transformation = union(enum) {
1216 /// Replace the fn decl AST Node with one whose body is only `@trap()` with
......@@ -14,23 +18,51 @@ pub const Transformation = union(enum) {
1418 gut_function: Ast.Node.Index,
1519 /// Omit a global declaration.
1620 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 },
1728 /// Replace an expression with `undefined`.
1829 replace_with_undef: Ast.Node.Index,
30 /// Replace an `@import` with the imported file contents wrapped in a struct.
31 inline_imported_file: InlineImportedFile,
32
33 pub const InlineImportedFile = struct {
34 builtin_call_node: Ast.Node.Index,
35 imported_string: []const u8,
36 /// Identifier names that must be renamed in the inlined code or else
37 /// will cause ambiguous reference errors.
38 in_scope_names: std.StringArrayHashMapUnmanaged(void),
39 };
1940};
2041
2142pub const Error = error{OutOfMemory};
2243
2344/// The result will be priority shuffled.
24pub fn findTransformations(ast: *const Ast, transformations: *std.ArrayList(Transformation)) !void {
45pub fn findTransformations(
46 arena: std.mem.Allocator,
47 ast: *const Ast,
48 transformations: *std.ArrayList(Transformation),
49) !void {
2550 transformations.clearRetainingCapacity();
2651
2752 var walk: Walk = .{
2853 .ast = ast,
2954 .transformations = transformations,
3055 .gpa = transformations.allocator,
56 .arena = arena,
3157 .unreferenced_globals = .{},
58 .in_scope_names = .{},
59 .replace_names = .{},
3260 };
33 defer walk.unreferenced_globals.deinit(walk.gpa);
61 defer {
62 walk.unreferenced_globals.deinit(walk.gpa);
63 walk.in_scope_names.deinit(walk.gpa);
64 walk.replace_names.deinit(walk.gpa);
65 }
3466
3567 try walkMembers(&walk, walk.ast.rootDecls());
3668
......@@ -43,14 +75,18 @@ pub fn findTransformations(ast: *const Ast, transformations: *std.ArrayList(Tran
4375
4476fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void {
4577 // First we scan for globals so that we can delete them while walking.
46 try scanDecls(w, members);
78 try scanDecls(w, members, .add);
4779
4880 for (members) |member| {
4981 try walkMember(w, member);
5082 }
83
84 try scanDecls(w, members, .remove);
5185}
5286
53fn scanDecls(w: *Walk, members: []const Ast.Node.Index) Error!void {
87const ScanDeclsAction = enum { add, remove };
88
89fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void {
5490 const ast = w.ast;
5591 const gpa = w.gpa;
5692 const node_tags = ast.nodes.items(.tag);
......@@ -74,9 +110,27 @@ fn scanDecls(w: *Walk, members: []const Ast.Node.Index) Error!void {
74110
75111 else => continue,
76112 };
113
77114 assert(token_tags[name_token] == .identifier);
78115 const name_bytes = ast.tokenSlice(name_token);
79 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
116
117 switch (action) {
118 .add => {
119 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
120
121 const gop = try w.in_scope_names.getOrPut(gpa, name_bytes);
122 if (!gop.found_existing) gop.value_ptr.* = 0;
123 gop.value_ptr.* += 1;
124 },
125 .remove => {
126 const entry = w.in_scope_names.getEntry(name_bytes).?;
127 if (entry.value_ptr.* <= 1) {
128 assert(w.in_scope_names.swapRemove(name_bytes));
129 } else {
130 entry.value_ptr.* -= 1;
131 }
132 },
133 }
80134 }
81135}
82136
......@@ -89,9 +143,10 @@ fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
89143 try walkExpression(w, fn_proto);
90144 const body_node = datas[decl].rhs;
91145 if (!isFnBodyGutted(ast, body_node)) {
146 w.replace_names.clearRetainingCapacity();
92147 try w.transformations.append(.{ .gut_function = decl });
148 try walkExpression(w, body_node);
93149 }
94 try walkExpression(w, body_node);
95150 },
96151 .fn_proto_simple,
97152 .fn_proto_multi,
......@@ -121,7 +176,10 @@ fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
121176 .container_field_init,
122177 .container_field_align,
123178 .container_field,
124 => try walkContainerField(w, ast.fullContainerField(decl).?),
179 => {
180 try w.transformations.append(.{ .delete_node = decl });
181 try walkContainerField(w, ast.fullContainerField(decl).?);
182 },
125183
126184 .@"comptime" => {
127185 try w.transformations.append(.{ .delete_node = decl });
......@@ -140,7 +198,15 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
140198 const node_tags = ast.nodes.items(.tag);
141199 const datas = ast.nodes.items(.data);
142200 switch (node_tags[node]) {
143 .identifier => try walkIdentifier(w, main_tokens[node]),
201 .identifier => {
202 const name_ident = main_tokens[node];
203 assert(token_tags[name_ident] == .identifier);
204 const name_bytes = ast.tokenSlice(name_ident);
205 _ = w.unreferenced_globals.swapRemove(name_bytes);
206 if (w.replace_names.get(name_bytes)) |index| {
207 try w.transformations.items[index].delete_var_decl.references.append(w.arena, node);
208 }
209 },
144210
145211 .number_literal,
146212 .char_literal,
......@@ -437,16 +503,16 @@ fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
437503
438504 .builtin_call_two, .builtin_call_two_comma => {
439505 if (datas[node].lhs == 0) {
440 return walkBuiltinCall(w, main_tokens[node], &.{});
506 return walkBuiltinCall(w, node, &.{});
441507 } else if (datas[node].rhs == 0) {
442 return walkBuiltinCall(w, main_tokens[node], &.{datas[node].lhs});
508 return walkBuiltinCall(w, node, &.{datas[node].lhs});
443509 } else {
444 return walkBuiltinCall(w, main_tokens[node], &.{ datas[node].lhs, datas[node].rhs });
510 return walkBuiltinCall(w, node, &.{ datas[node].lhs, datas[node].rhs });
445511 }
446512 },
447513 .builtin_call, .builtin_call_comma => {
448514 const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
449 return walkBuiltinCall(w, main_tokens[node], params);
515 return walkBuiltinCall(w, node, params);
450516 },
451517
452518 .fn_proto_simple,
......@@ -537,9 +603,12 @@ fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.Var
537603 try walkExpression(w, var_decl.ast.section_node);
538604 }
539605
540 assert(var_decl.ast.init_node != 0);
541
542 return walkExpression(w, var_decl.ast.init_node);
606 if (var_decl.ast.init_node != 0) {
607 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
608 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
609 }
610 try walkExpression(w, var_decl.ast.init_node);
611 }
543612}
544613
545614fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
......@@ -561,12 +630,12 @@ fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
561630 try walkExpression(w, var_decl.ast.section_node);
562631 }
563632
564 assert(var_decl.ast.init_node != 0);
565 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
566 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
633 if (var_decl.ast.init_node != 0) {
634 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
635 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
636 }
637 try walkExpression(w, var_decl.ast.init_node);
567638 }
568
569 return walkExpression(w, var_decl.ast.init_node);
570639}
571640
572641fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
......@@ -576,7 +645,9 @@ fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
576645 if (field.ast.align_expr != 0) {
577646 try walkExpression(w, field.ast.align_expr); // alignment
578647 }
579 try walkExpression(w, field.ast.value_expr); // value
648 if (field.ast.value_expr != 0) {
649 try walkExpression(w, field.ast.value_expr); // value
650 }
580651}
581652
582653fn walkBlock(
......@@ -594,9 +665,34 @@ fn walkBlock(
594665 .local_var_decl,
595666 .simple_var_decl,
596667 .aligned_var_decl,
597 => try walkLocalVarDecl(w, ast.fullVarDecl(stmt).?),
598
599 else => try walkExpression(w, stmt),
668 => {
669 const var_decl = ast.fullVarDecl(stmt).?;
670 if (var_decl.ast.init_node != 0 and
671 isUndefinedIdent(w.ast, var_decl.ast.init_node))
672 {
673 try w.transformations.append(.{ .delete_var_decl = .{
674 .var_decl_node = stmt,
675 .references = .{},
676 } });
677 const name_tok = var_decl.ast.mut_token + 1;
678 const name_bytes = ast.tokenSlice(name_tok);
679 try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1));
680 } else {
681 try walkLocalVarDecl(w, var_decl);
682 }
683 },
684
685 else => {
686 switch (categorizeStmt(ast, stmt)) {
687 // Don't try to remove `_ = foo;` discards; those are handled separately.
688 .discard_identifier => {},
689 // definitely try to remove `_ = undefined;` though.
690 .discard_undefined, .trap_call, .other => {
691 try w.transformations.append(.{ .delete_node = stmt });
692 },
693 }
694 try walkExpression(w, stmt);
695 },
600696 }
601697 }
602698}
......@@ -680,10 +776,35 @@ fn walkContainerDecl(
680776
681777fn walkBuiltinCall(
682778 w: *Walk,
683 builtin_token: Ast.TokenIndex,
779 call_node: Ast.Node.Index,
684780 params: []const Ast.Node.Index,
685781) Error!void {
686 _ = builtin_token;
782 const ast = w.ast;
783 const main_tokens = ast.nodes.items(.main_token);
784 const builtin_token = main_tokens[call_node];
785 const builtin_name = ast.tokenSlice(builtin_token);
786 const info = BuiltinFn.list.get(builtin_name).?;
787 switch (info.tag) {
788 .import => {
789 const operand_node = params[0];
790 const str_lit_token = main_tokens[operand_node];
791 const token_bytes = ast.tokenSlice(str_lit_token);
792 if (std.mem.endsWith(u8, token_bytes, ".zig\"")) {
793 const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch
794 unreachable;
795 try w.transformations.append(.{ .inline_imported_file = .{
796 .builtin_call_node = call_node,
797 .imported_string = imported_string,
798 .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init(
799 w.arena,
800 w.in_scope_names.keys(),
801 &.{},
802 ),
803 } });
804 }
805 },
806 else => {},
807 }
687808 for (params) |param_node| {
688809 try walkExpression(w, param_node);
689810 }
......@@ -821,6 +942,7 @@ fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
821942}
822943
823944const StmtCategory = enum {
945 discard_undefined,
824946 discard_identifier,
825947 trap_call,
826948 other,
......@@ -846,8 +968,14 @@ fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
846968 },
847969 .assign => {
848970 const infix = datas[stmt];
849 if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier)
850 return .discard_identifier;
971 if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier) {
972 const name_bytes = ast.tokenSlice(main_tokens[infix.rhs]);
973 if (std.mem.eql(u8, name_bytes, "undefined")) {
974 return .discard_undefined;
975 } else {
976 return .discard_identifier;
977 }
978 }
851979 return .other;
852980 },
853981 else => return .other,
......@@ -867,26 +995,21 @@ fn categorizeBuiltinCall(
867995}
868996
869997fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
870 const node_tags = ast.nodes.items(.tag);
871 const main_tokens = ast.nodes.items(.main_token);
872 switch (node_tags[node]) {
873 .identifier => {
874 const token_index = main_tokens[node];
875 const name_bytes = ast.tokenSlice(token_index);
876 return std.mem.eql(u8, name_bytes, "_");
877 },
878 else => return false,
879 }
998 return isMatchingIdent(ast, node, "_");
880999}
8811000
8821001fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1002 return isMatchingIdent(ast, node, "undefined");
1003}
1004
1005fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
8831006 const node_tags = ast.nodes.items(.tag);
8841007 const main_tokens = ast.nodes.items(.main_token);
8851008 switch (node_tags[node]) {
8861009 .identifier => {
8871010 const token_index = main_tokens[node];
8881011 const name_bytes = ast.tokenSlice(token_index);
889 return std.mem.eql(u8, name_bytes, "undefined");
1012 return std.mem.eql(u8, name_bytes, string);
8901013 },
8911014 else => return false,
8921015 }