mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
Merge pull request #17852 from ziglang/zig-reduce
introduce `zig reduce` subcommand
This commit is contained in:
commit
98dc28bbe2
@ -33,6 +33,7 @@ pub fn build(b: *std.Build) !void {
|
||||
const skip_install_langref = b.option(bool, "no-langref", "skip copying of langref to the installation prefix") orelse skip_install_lib_files;
|
||||
const skip_install_autodocs = b.option(bool, "no-autodocs", "skip copying of standard library autodocs to the installation prefix") orelse skip_install_lib_files;
|
||||
const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false;
|
||||
const only_reduce = b.option(bool, "only-reduce", "only build zig reduce") orelse false;
|
||||
|
||||
const docgen_exe = b.addExecutable(.{
|
||||
.name = "docgen",
|
||||
@ -236,6 +237,7 @@ pub fn build(b: *std.Build) !void {
|
||||
exe_options.addOption(bool, "force_gpa", force_gpa);
|
||||
exe_options.addOption(bool, "only_c", only_c);
|
||||
exe_options.addOption(bool, "only_core_functionality", only_c);
|
||||
exe_options.addOption(bool, "only_reduce", only_reduce);
|
||||
|
||||
if (link_libc) {
|
||||
exe.linkLibC();
|
||||
@ -391,6 +393,7 @@ pub fn build(b: *std.Build) !void {
|
||||
test_cases_options.addOption(bool, "force_gpa", force_gpa);
|
||||
test_cases_options.addOption(bool, "only_c", only_c);
|
||||
test_cases_options.addOption(bool, "only_core_functionality", true);
|
||||
test_cases_options.addOption(bool, "only_reduce", false);
|
||||
test_cases_options.addOption(bool, "enable_qemu", b.enable_qemu);
|
||||
test_cases_options.addOption(bool, "enable_wine", b.enable_wine);
|
||||
test_cases_options.addOption(bool, "enable_wasmtime", b.enable_wasmtime);
|
||||
@ -549,6 +552,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
|
||||
exe_options.addOption(bool, "enable_tracy_allocation", false);
|
||||
exe_options.addOption(bool, "value_tracing", false);
|
||||
exe_options.addOption(bool, "only_core_functionality", true);
|
||||
exe_options.addOption(bool, "only_reduce", false);
|
||||
|
||||
const run_opt = b.addSystemCommand(&.{
|
||||
"wasm-opt",
|
||||
|
||||
@ -203,7 +203,7 @@ pub fn main() !void {
|
||||
usageAndErr(builder, false, stderr_stream);
|
||||
};
|
||||
seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
|
||||
std.debug.print("unable to parse seed '{s}' as 32-bit integer: {s}", .{
|
||||
std.debug.print("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
|
||||
next_arg, @errorName(err),
|
||||
});
|
||||
process.exit(1);
|
||||
|
||||
@ -859,6 +859,18 @@ pub const DynamicBitSetUnmanaged = struct {
|
||||
self.masks[maskIndex(index)] &= ~maskBit(index);
|
||||
}
|
||||
|
||||
/// Set all bits to 0.
|
||||
pub fn unsetAll(self: *Self) void {
|
||||
const masks_len = numMasks(self.bit_length);
|
||||
@memset(self.masks[0..masks_len], 0);
|
||||
}
|
||||
|
||||
/// Set all bits to 1.
|
||||
pub fn setAll(self: *Self) void {
|
||||
const masks_len = numMasks(self.bit_length);
|
||||
@memset(self.masks[0..masks_len], std.math.maxInt(MaskInt));
|
||||
}
|
||||
|
||||
/// Flips a specific bit in the bit set
|
||||
pub fn toggle(self: *Self, index: usize) void {
|
||||
assert(index < self.bit_length);
|
||||
|
||||
@ -113,12 +113,14 @@ pub fn render(tree: Ast, gpa: Allocator) RenderError![]u8 {
|
||||
var buffer = std.ArrayList(u8).init(gpa);
|
||||
defer buffer.deinit();
|
||||
|
||||
try tree.renderToArrayList(&buffer);
|
||||
try tree.renderToArrayList(&buffer, .{});
|
||||
return buffer.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn renderToArrayList(tree: Ast, buffer: *std.ArrayList(u8)) RenderError!void {
|
||||
return @import("./render.zig").renderTree(buffer, tree);
|
||||
pub const Fixups = private_render.Fixups;
|
||||
|
||||
pub fn renderToArrayList(tree: Ast, buffer: *std.ArrayList(u8), fixups: Fixups) RenderError!void {
|
||||
return @import("./render.zig").renderTree(buffer, tree, fixups);
|
||||
}
|
||||
|
||||
/// Returns an extra offset for column and byte offset of errors that
|
||||
@ -3530,6 +3532,7 @@ const Token = std.zig.Token;
|
||||
const Ast = @This();
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Parse = @import("Parse.zig");
|
||||
const private_render = @import("./render.zig");
|
||||
|
||||
test {
|
||||
testing.refAllDecls(@This());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
12
src/main.zig
12
src/main.zig
@ -212,6 +212,14 @@ pub fn main() anyerror!void {
|
||||
}
|
||||
}
|
||||
|
||||
if (build_options.only_reduce) {
|
||||
if (mem.eql(u8, args[1], "reduce")) {
|
||||
return @import("reduce.zig").main(gpa, arena, args);
|
||||
} else {
|
||||
@panic("only reduce is supported in a -Donly-reduce build");
|
||||
}
|
||||
}
|
||||
|
||||
return mainArgs(gpa, arena, args);
|
||||
}
|
||||
|
||||
@ -328,6 +336,8 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
|
||||
} else if (mem.eql(u8, cmd, "env")) {
|
||||
verifyLibcxxCorrectlyLinked();
|
||||
return @import("print_env.zig").cmdEnv(arena, cmd_args, io.getStdOut().writer());
|
||||
} else if (mem.eql(u8, cmd, "reduce")) {
|
||||
return @import("reduce.zig").main(gpa, arena, args);
|
||||
} else if (mem.eql(u8, cmd, "zen")) {
|
||||
return io.getStdOut().writeAll(info_zen);
|
||||
} else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) {
|
||||
@ -5766,7 +5776,7 @@ fn fmtPathFile(
|
||||
fmt.out_buffer.shrinkRetainingCapacity(0);
|
||||
try fmt.out_buffer.ensureTotalCapacity(source_code.len);
|
||||
|
||||
try tree.renderToArrayList(&fmt.out_buffer);
|
||||
try tree.renderToArrayList(&fmt.out_buffer, .{});
|
||||
if (mem.eql(u8, fmt.out_buffer.items, source_code))
|
||||
return;
|
||||
|
||||
|
||||
280
src/reduce.zig
Normal file
280
src/reduce.zig
Normal file
@ -0,0 +1,280 @@
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const fatal = @import("./main.zig").fatal;
|
||||
const Ast = std.zig.Ast;
|
||||
const Walk = @import("reduce/Walk.zig");
|
||||
|
||||
const usage =
|
||||
\\zig reduce [options] ./checker root_source_file.zig [-- [argv]]
|
||||
\\
|
||||
\\root_source_file.zig is relative to --main-mod-path.
|
||||
\\
|
||||
\\checker:
|
||||
\\ An executable that communicates interestingness by returning these exit codes:
|
||||
\\ exit(0): interesting
|
||||
\\ exit(1): unknown (infinite loop or other mishap)
|
||||
\\ exit(other): not interesting
|
||||
\\
|
||||
\\options:
|
||||
\\ --seed [integer] Override the random seed. Defaults to 0
|
||||
\\ --skip-smoke-test Skip interestingness check smoke test
|
||||
\\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name
|
||||
\\ deps: [dep],[dep],...
|
||||
\\ dep: [[import=]name]
|
||||
\\ --deps [dep],[dep],... Set dependency names for the root package
|
||||
\\ dep: [[import=]name]
|
||||
\\ --main-mod-path Set the directory of the root module
|
||||
\\
|
||||
\\argv:
|
||||
\\ Forwarded directly to the interestingness script.
|
||||
\\
|
||||
;
|
||||
|
||||
const Interestingness = enum { interesting, unknown, boring };
|
||||
|
||||
// Roadmap:
|
||||
// - add thread pool
|
||||
// - add support for parsing the module flags
|
||||
// - more fancy transformations
|
||||
// - @import inlining of modules
|
||||
// - @import inlining of files
|
||||
// - deleting unused functions and other globals
|
||||
// - removing statements or blocks of code
|
||||
// - replacing operands of `and` and `or` with `true` and `false`
|
||||
// - replacing if conditions with `true` and `false`
|
||||
// - reduce flags sent to the compiler
|
||||
// - integrate with the build system?
|
||||
|
||||
pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
|
||||
var opt_checker_path: ?[]const u8 = null;
|
||||
var opt_root_source_file_path: ?[]const u8 = null;
|
||||
var argv: []const []const u8 = &.{};
|
||||
var seed: u32 = 0;
|
||||
var skip_smoke_test = false;
|
||||
|
||||
{
|
||||
var i: usize = 2; // skip over "zig" and "reduce"
|
||||
while (i < args.len) : (i += 1) {
|
||||
const arg = args[i];
|
||||
if (mem.startsWith(u8, arg, "-")) {
|
||||
if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
try stdout.writeAll(usage);
|
||||
return std.process.cleanExit();
|
||||
} else if (mem.eql(u8, arg, "--")) {
|
||||
argv = args[i + 1 ..];
|
||||
break;
|
||||
} else if (mem.eql(u8, arg, "--skip-smoke-test")) {
|
||||
skip_smoke_test = true;
|
||||
} else if (mem.eql(u8, arg, "--main-mod-path")) {
|
||||
@panic("TODO: implement --main-mod-path");
|
||||
} else if (mem.eql(u8, arg, "--mod")) {
|
||||
@panic("TODO: implement --mod");
|
||||
} else if (mem.eql(u8, arg, "--deps")) {
|
||||
@panic("TODO: implement --deps");
|
||||
} else if (mem.eql(u8, arg, "--seed")) {
|
||||
i += 1;
|
||||
if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});
|
||||
const next_arg = args[i];
|
||||
seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
|
||||
fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{
|
||||
next_arg, @errorName(err),
|
||||
});
|
||||
};
|
||||
} else {
|
||||
fatal("unrecognized parameter: '{s}'", .{arg});
|
||||
}
|
||||
} else if (opt_checker_path == null) {
|
||||
opt_checker_path = arg;
|
||||
} else if (opt_root_source_file_path == null) {
|
||||
opt_root_source_file_path = arg;
|
||||
} else {
|
||||
fatal("unexpected extra parameter: '{s}'", .{arg});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const checker_path = opt_checker_path orelse
|
||||
fatal("missing interestingness checker argument; see -h for usage", .{});
|
||||
const root_source_file_path = opt_root_source_file_path orelse
|
||||
fatal("missing root source file path argument; see -h for usage", .{});
|
||||
|
||||
var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{};
|
||||
try interestingness_argv.ensureUnusedCapacity(arena, argv.len + 1);
|
||||
interestingness_argv.appendAssumeCapacity(checker_path);
|
||||
interestingness_argv.appendSliceAssumeCapacity(argv);
|
||||
|
||||
var rendered = std.ArrayList(u8).init(gpa);
|
||||
defer rendered.deinit();
|
||||
|
||||
var tree = try parse(gpa, arena, root_source_file_path);
|
||||
defer tree.deinit(gpa);
|
||||
|
||||
if (!skip_smoke_test) {
|
||||
std.debug.print("smoke testing the interestingness check...\n", .{});
|
||||
switch (try runCheck(arena, interestingness_argv.items)) {
|
||||
.interesting => {},
|
||||
.boring, .unknown => |t| {
|
||||
fatal("interestingness check returned {s} for unmodified input\n", .{
|
||||
@tagName(t),
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var fixups: Ast.Fixups = .{};
|
||||
defer fixups.deinit(gpa);
|
||||
var rng = std.rand.DefaultPrng.init(seed);
|
||||
|
||||
// 1. Walk the AST of the source file looking for independent
|
||||
// reductions and collecting them all into an array list.
|
||||
// 2. Randomize the list of transformations. A future enhancement will add
|
||||
// priority weights to the sorting but for now they are completely
|
||||
// shuffled.
|
||||
// 3. Apply a subset consisting of 1/2 of the transformations and check for
|
||||
// interestingness.
|
||||
// 4. If not interesting, half the subset size again and check again.
|
||||
// 5. Repeat until the subset size is 1, then march the transformation
|
||||
// index forward by 1 with each non-interesting attempt.
|
||||
//
|
||||
// At any point if a subset of transformations succeeds in producing an interesting
|
||||
// result, restart the whole process, reparsing the AST and re-generating the list
|
||||
// of all possible transformations and shuffling it again.
|
||||
|
||||
var transformations = std.ArrayList(Walk.Transformation).init(gpa);
|
||||
defer transformations.deinit();
|
||||
try Walk.findTransformations(&tree, &transformations);
|
||||
sortTransformations(transformations.items, rng.random());
|
||||
|
||||
fresh: while (transformations.items.len > 0) {
|
||||
std.debug.print("found {d} possible transformations\n", .{
|
||||
transformations.items.len,
|
||||
});
|
||||
var subset_size: usize = transformations.items.len;
|
||||
var start_index: usize = 0;
|
||||
|
||||
while (start_index < transformations.items.len) {
|
||||
subset_size = @max(1, subset_size / 2);
|
||||
|
||||
const this_set = transformations.items[start_index..][0..subset_size];
|
||||
try transformationsToFixups(gpa, this_set, &fixups);
|
||||
|
||||
rendered.clearRetainingCapacity();
|
||||
try tree.renderToArrayList(&rendered, fixups);
|
||||
try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
|
||||
|
||||
const interestingness = try runCheck(arena, interestingness_argv.items);
|
||||
std.debug.print("{d} random transformations: {s}. {d} remaining\n", .{
|
||||
subset_size, @tagName(interestingness), transformations.items.len - start_index,
|
||||
});
|
||||
switch (interestingness) {
|
||||
.interesting => {
|
||||
const new_tree = try parse(gpa, arena, root_source_file_path);
|
||||
tree.deinit(gpa);
|
||||
tree = new_tree;
|
||||
|
||||
try Walk.findTransformations(&tree, &transformations);
|
||||
// Resetting based on the seed again means we will get the same
|
||||
// results if restarting the reduction process from this new point.
|
||||
rng = std.rand.DefaultPrng.init(seed);
|
||||
sortTransformations(transformations.items, rng.random());
|
||||
|
||||
continue :fresh;
|
||||
},
|
||||
.unknown, .boring => {
|
||||
// Continue to try the next set of transformations.
|
||||
// If we tested only one transformation, move on to the next one.
|
||||
if (subset_size == 1) {
|
||||
start_index += 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
std.debug.print("all {d} remaining transformations are uninteresting\n", .{
|
||||
transformations.items.len,
|
||||
});
|
||||
|
||||
// Revert the source back to not be transformed.
|
||||
fixups.clearRetainingCapacity();
|
||||
rendered.clearRetainingCapacity();
|
||||
try tree.renderToArrayList(&rendered, fixups);
|
||||
try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
|
||||
|
||||
return std.process.cleanExit();
|
||||
}
|
||||
std.debug.print("no more transformations found\n", .{});
|
||||
return std.process.cleanExit();
|
||||
}
|
||||
|
||||
fn sortTransformations(transformations: []Walk.Transformation, rng: std.rand.Random) void {
|
||||
rng.shuffle(Walk.Transformation, transformations);
|
||||
// Stable sort based on priority to keep randomness as the secondary sort.
|
||||
// TODO: introduce transformation priorities
|
||||
// std.mem.sort(transformations);
|
||||
}
|
||||
|
||||
fn termToInteresting(term: std.process.Child.Term) Interestingness {
|
||||
return switch (term) {
|
||||
.Exited => |code| switch (code) {
|
||||
0 => .interesting,
|
||||
1 => .unknown,
|
||||
else => .boring,
|
||||
},
|
||||
else => b: {
|
||||
std.debug.print("interestingness check aborted unexpectedly\n", .{});
|
||||
break :b .boring;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness {
|
||||
const result = try std.process.Child.run(.{
|
||||
.allocator = arena,
|
||||
.argv = argv,
|
||||
});
|
||||
if (result.stderr.len != 0)
|
||||
std.debug.print("{s}", .{result.stderr});
|
||||
return termToInteresting(result.term);
|
||||
}
|
||||
|
||||
fn transformationsToFixups(
|
||||
gpa: Allocator,
|
||||
transforms: []const Walk.Transformation,
|
||||
fixups: *Ast.Fixups,
|
||||
) !void {
|
||||
fixups.clearRetainingCapacity();
|
||||
|
||||
for (transforms) |t| switch (t) {
|
||||
.gut_function => |fn_decl_node| {
|
||||
try fixups.gut_functions.put(gpa, fn_decl_node, {});
|
||||
},
|
||||
.delete_node => |decl_node| {
|
||||
try fixups.omit_nodes.put(gpa, decl_node, {});
|
||||
},
|
||||
.replace_with_undef => |node| {
|
||||
try fixups.replace_nodes.put(gpa, node, {});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn parse(gpa: Allocator, arena: Allocator, root_source_file_path: []const u8) !Ast {
|
||||
const source_code = try std.fs.cwd().readFileAllocOptions(
|
||||
arena,
|
||||
root_source_file_path,
|
||||
std.math.maxInt(u32),
|
||||
null,
|
||||
1,
|
||||
0,
|
||||
);
|
||||
|
||||
var tree = try Ast.parse(gpa, source_code, .zig);
|
||||
errdefer tree.deinit(gpa);
|
||||
|
||||
if (tree.errors.len != 0) {
|
||||
@panic("syntax errors occurred");
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
893
src/reduce/Walk.zig
Normal file
893
src/reduce/Walk.zig
Normal file
@ -0,0 +1,893 @@
|
||||
const std = @import("std");
|
||||
const Ast = std.zig.Ast;
|
||||
const Walk = @This();
|
||||
const assert = std.debug.assert;
|
||||
|
||||
ast: *const Ast,
|
||||
transformations: *std.ArrayList(Transformation),
|
||||
unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
|
||||
gpa: std.mem.Allocator,
|
||||
|
||||
pub const Transformation = union(enum) {
|
||||
/// Replace the fn decl AST Node with one whose body is only `@trap()` with
|
||||
/// discarded parameters.
|
||||
gut_function: Ast.Node.Index,
|
||||
/// Omit a global declaration.
|
||||
delete_node: Ast.Node.Index,
|
||||
/// Replace an expression with `undefined`.
|
||||
replace_with_undef: Ast.Node.Index,
|
||||
};
|
||||
|
||||
pub const Error = error{OutOfMemory};
|
||||
|
||||
/// The result will be priority shuffled.
|
||||
pub fn findTransformations(ast: *const Ast, transformations: *std.ArrayList(Transformation)) !void {
|
||||
transformations.clearRetainingCapacity();
|
||||
|
||||
var walk: Walk = .{
|
||||
.ast = ast,
|
||||
.transformations = transformations,
|
||||
.gpa = transformations.allocator,
|
||||
.unreferenced_globals = .{},
|
||||
};
|
||||
defer walk.unreferenced_globals.deinit(walk.gpa);
|
||||
|
||||
try walkMembers(&walk, walk.ast.rootDecls());
|
||||
|
||||
const unreferenced_globals = walk.unreferenced_globals.values();
|
||||
try transformations.ensureUnusedCapacity(unreferenced_globals.len);
|
||||
for (unreferenced_globals) |node| {
|
||||
transformations.appendAssumeCapacity(.{ .delete_node = node });
|
||||
}
|
||||
}
|
||||
|
||||
fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void {
|
||||
// First we scan for globals so that we can delete them while walking.
|
||||
try scanDecls(w, members);
|
||||
|
||||
for (members) |member| {
|
||||
try walkMember(w, member);
|
||||
}
|
||||
}
|
||||
|
||||
fn scanDecls(w: *Walk, members: []const Ast.Node.Index) Error!void {
|
||||
const ast = w.ast;
|
||||
const gpa = w.gpa;
|
||||
const node_tags = ast.nodes.items(.tag);
|
||||
const main_tokens = ast.nodes.items(.main_token);
|
||||
const token_tags = ast.tokens.items(.tag);
|
||||
|
||||
for (members) |member_node| {
|
||||
const name_token = switch (node_tags[member_node]) {
|
||||
.global_var_decl,
|
||||
.local_var_decl,
|
||||
.simple_var_decl,
|
||||
.aligned_var_decl,
|
||||
=> main_tokens[member_node] + 1,
|
||||
|
||||
.fn_proto_simple,
|
||||
.fn_proto_multi,
|
||||
.fn_proto_one,
|
||||
.fn_proto,
|
||||
.fn_decl,
|
||||
=> main_tokens[member_node] + 1,
|
||||
|
||||
else => continue,
|
||||
};
|
||||
assert(token_tags[name_token] == .identifier);
|
||||
const name_bytes = ast.tokenSlice(name_token);
|
||||
try w.unreferenced_globals.put(gpa, name_bytes, member_node);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
|
||||
const ast = w.ast;
|
||||
const datas = ast.nodes.items(.data);
|
||||
switch (ast.nodes.items(.tag)[decl]) {
|
||||
.fn_decl => {
|
||||
const fn_proto = datas[decl].lhs;
|
||||
try walkExpression(w, fn_proto);
|
||||
const body_node = datas[decl].rhs;
|
||||
if (!isFnBodyGutted(ast, body_node)) {
|
||||
try w.transformations.append(.{ .gut_function = decl });
|
||||
}
|
||||
try walkExpression(w, body_node);
|
||||
},
|
||||
.fn_proto_simple,
|
||||
.fn_proto_multi,
|
||||
.fn_proto_one,
|
||||
.fn_proto,
|
||||
=> {
|
||||
try walkExpression(w, decl);
|
||||
},
|
||||
|
||||
.@"usingnamespace" => {
|
||||
try w.transformations.append(.{ .delete_node = decl });
|
||||
const expr = datas[decl].lhs;
|
||||
try walkExpression(w, expr);
|
||||
},
|
||||
|
||||
.global_var_decl,
|
||||
.local_var_decl,
|
||||
.simple_var_decl,
|
||||
.aligned_var_decl,
|
||||
=> try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?),
|
||||
|
||||
.test_decl => {
|
||||
try w.transformations.append(.{ .delete_node = decl });
|
||||
try walkExpression(w, datas[decl].rhs);
|
||||
},
|
||||
|
||||
.container_field_init,
|
||||
.container_field_align,
|
||||
.container_field,
|
||||
=> try walkContainerField(w, ast.fullContainerField(decl).?),
|
||||
|
||||
.@"comptime" => {
|
||||
try w.transformations.append(.{ .delete_node = decl });
|
||||
try walkExpression(w, decl);
|
||||
},
|
||||
|
||||
.root => unreachable,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
|
||||
const ast = w.ast;
|
||||
const token_tags = ast.tokens.items(.tag);
|
||||
const main_tokens = ast.nodes.items(.main_token);
|
||||
const node_tags = ast.nodes.items(.tag);
|
||||
const datas = ast.nodes.items(.data);
|
||||
switch (node_tags[node]) {
|
||||
.identifier => try walkIdentifier(w, main_tokens[node]),
|
||||
|
||||
.number_literal,
|
||||
.char_literal,
|
||||
.unreachable_literal,
|
||||
.anyframe_literal,
|
||||
.string_literal,
|
||||
=> {},
|
||||
|
||||
.multiline_string_literal => {},
|
||||
|
||||
.error_value => {},
|
||||
|
||||
.block_two,
|
||||
.block_two_semicolon,
|
||||
=> {
|
||||
const statements = [2]Ast.Node.Index{ datas[node].lhs, datas[node].rhs };
|
||||
if (datas[node].lhs == 0) {
|
||||
return walkBlock(w, node, statements[0..0]);
|
||||
} else if (datas[node].rhs == 0) {
|
||||
return walkBlock(w, node, statements[0..1]);
|
||||
} else {
|
||||
return walkBlock(w, node, statements[0..2]);
|
||||
}
|
||||
},
|
||||
.block,
|
||||
.block_semicolon,
|
||||
=> {
|
||||
const statements = ast.extra_data[datas[node].lhs..datas[node].rhs];
|
||||
return walkBlock(w, node, statements);
|
||||
},
|
||||
|
||||
.@"errdefer" => {
|
||||
const expr = datas[node].rhs;
|
||||
return walkExpression(w, expr);
|
||||
},
|
||||
|
||||
.@"defer" => {
|
||||
const expr = datas[node].rhs;
|
||||
return walkExpression(w, expr);
|
||||
},
|
||||
.@"comptime", .@"nosuspend" => {
|
||||
const block = datas[node].lhs;
|
||||
return walkExpression(w, block);
|
||||
},
|
||||
|
||||
.@"suspend" => {
|
||||
const body = datas[node].lhs;
|
||||
return walkExpression(w, body);
|
||||
},
|
||||
|
||||
.@"catch" => {
|
||||
try walkExpression(w, datas[node].lhs); // target
|
||||
try walkExpression(w, datas[node].rhs); // fallback
|
||||
},
|
||||
|
||||
.field_access => {
|
||||
const field_access = datas[node];
|
||||
try walkExpression(w, field_access.lhs);
|
||||
},
|
||||
|
||||
.error_union,
|
||||
.switch_range,
|
||||
=> {
|
||||
const infix = datas[node];
|
||||
try walkExpression(w, infix.lhs);
|
||||
return walkExpression(w, infix.rhs);
|
||||
},
|
||||
.for_range => {
|
||||
const infix = datas[node];
|
||||
try walkExpression(w, infix.lhs);
|
||||
if (infix.rhs != 0) {
|
||||
return walkExpression(w, infix.rhs);
|
||||
}
|
||||
},
|
||||
|
||||
.add,
|
||||
.add_wrap,
|
||||
.add_sat,
|
||||
.array_cat,
|
||||
.array_mult,
|
||||
.assign,
|
||||
.assign_bit_and,
|
||||
.assign_bit_or,
|
||||
.assign_shl,
|
||||
.assign_shl_sat,
|
||||
.assign_shr,
|
||||
.assign_bit_xor,
|
||||
.assign_div,
|
||||
.assign_sub,
|
||||
.assign_sub_wrap,
|
||||
.assign_sub_sat,
|
||||
.assign_mod,
|
||||
.assign_add,
|
||||
.assign_add_wrap,
|
||||
.assign_add_sat,
|
||||
.assign_mul,
|
||||
.assign_mul_wrap,
|
||||
.assign_mul_sat,
|
||||
.bang_equal,
|
||||
.bit_and,
|
||||
.bit_or,
|
||||
.shl,
|
||||
.shl_sat,
|
||||
.shr,
|
||||
.bit_xor,
|
||||
.bool_and,
|
||||
.bool_or,
|
||||
.div,
|
||||
.equal_equal,
|
||||
.greater_or_equal,
|
||||
.greater_than,
|
||||
.less_or_equal,
|
||||
.less_than,
|
||||
.merge_error_sets,
|
||||
.mod,
|
||||
.mul,
|
||||
.mul_wrap,
|
||||
.mul_sat,
|
||||
.sub,
|
||||
.sub_wrap,
|
||||
.sub_sat,
|
||||
.@"orelse",
|
||||
=> {
|
||||
const infix = datas[node];
|
||||
try walkExpression(w, infix.lhs);
|
||||
try walkExpression(w, infix.rhs);
|
||||
},
|
||||
|
||||
.assign_destructure => {
|
||||
const lhs_count = ast.extra_data[datas[node].lhs];
|
||||
assert(lhs_count > 1);
|
||||
const lhs_exprs = ast.extra_data[datas[node].lhs + 1 ..][0..lhs_count];
|
||||
const rhs = datas[node].rhs;
|
||||
|
||||
for (lhs_exprs) |lhs_node| {
|
||||
switch (node_tags[lhs_node]) {
|
||||
.global_var_decl,
|
||||
.local_var_decl,
|
||||
.simple_var_decl,
|
||||
.aligned_var_decl,
|
||||
=> try walkLocalVarDecl(w, ast.fullVarDecl(lhs_node).?),
|
||||
|
||||
else => try walkExpression(w, lhs_node),
|
||||
}
|
||||
}
|
||||
return walkExpression(w, rhs);
|
||||
},
|
||||
|
||||
.bit_not,
|
||||
.bool_not,
|
||||
.negation,
|
||||
.negation_wrap,
|
||||
.optional_type,
|
||||
.address_of,
|
||||
=> {
|
||||
return walkExpression(w, datas[node].lhs);
|
||||
},
|
||||
|
||||
.@"try",
|
||||
.@"resume",
|
||||
.@"await",
|
||||
=> {
|
||||
return walkExpression(w, datas[node].lhs);
|
||||
},
|
||||
|
||||
.array_type,
|
||||
.array_type_sentinel,
|
||||
=> {},
|
||||
|
||||
.ptr_type_aligned,
|
||||
.ptr_type_sentinel,
|
||||
.ptr_type,
|
||||
.ptr_type_bit_range,
|
||||
=> {},
|
||||
|
||||
.array_init_one,
|
||||
.array_init_one_comma,
|
||||
.array_init_dot_two,
|
||||
.array_init_dot_two_comma,
|
||||
.array_init_dot,
|
||||
.array_init_dot_comma,
|
||||
.array_init,
|
||||
.array_init_comma,
|
||||
=> {
|
||||
var elements: [2]Ast.Node.Index = undefined;
|
||||
return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
|
||||
},
|
||||
|
||||
.struct_init_one,
|
||||
.struct_init_one_comma,
|
||||
.struct_init_dot_two,
|
||||
.struct_init_dot_two_comma,
|
||||
.struct_init_dot,
|
||||
.struct_init_dot_comma,
|
||||
.struct_init,
|
||||
.struct_init_comma,
|
||||
=> {
|
||||
var buf: [2]Ast.Node.Index = undefined;
|
||||
return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
|
||||
},
|
||||
|
||||
.call_one,
|
||||
.call_one_comma,
|
||||
.async_call_one,
|
||||
.async_call_one_comma,
|
||||
.call,
|
||||
.call_comma,
|
||||
.async_call,
|
||||
.async_call_comma,
|
||||
=> {
|
||||
var buf: [1]Ast.Node.Index = undefined;
|
||||
return walkCall(w, ast.fullCall(&buf, node).?);
|
||||
},
|
||||
|
||||
.array_access => {
|
||||
const suffix = datas[node];
|
||||
try walkExpression(w, suffix.lhs);
|
||||
try walkExpression(w, suffix.rhs);
|
||||
},
|
||||
|
||||
.slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
|
||||
|
||||
.deref => {
|
||||
try walkExpression(w, datas[node].lhs);
|
||||
},
|
||||
|
||||
.unwrap_optional => {
|
||||
try walkExpression(w, datas[node].lhs);
|
||||
},
|
||||
|
||||
.@"break" => {
|
||||
const label_token = datas[node].lhs;
|
||||
const target = datas[node].rhs;
|
||||
if (label_token == 0 and target == 0) {
|
||||
// no expressions
|
||||
} else if (label_token == 0 and target != 0) {
|
||||
try walkExpression(w, target);
|
||||
} else if (label_token != 0 and target == 0) {
|
||||
try walkIdentifier(w, label_token);
|
||||
} else if (label_token != 0 and target != 0) {
|
||||
try walkExpression(w, target);
|
||||
}
|
||||
},
|
||||
|
||||
.@"continue" => {
|
||||
const label = datas[node].lhs;
|
||||
if (label != 0) {
|
||||
return walkIdentifier(w, label); // label
|
||||
}
|
||||
},
|
||||
|
||||
.@"return" => {
|
||||
if (datas[node].lhs != 0) {
|
||||
try walkExpression(w, datas[node].lhs);
|
||||
}
|
||||
},
|
||||
|
||||
.grouped_expression => {
|
||||
try walkExpression(w, datas[node].lhs);
|
||||
},
|
||||
|
||||
.container_decl,
|
||||
.container_decl_trailing,
|
||||
.container_decl_arg,
|
||||
.container_decl_arg_trailing,
|
||||
.container_decl_two,
|
||||
.container_decl_two_trailing,
|
||||
.tagged_union,
|
||||
.tagged_union_trailing,
|
||||
.tagged_union_enum_tag,
|
||||
.tagged_union_enum_tag_trailing,
|
||||
.tagged_union_two,
|
||||
.tagged_union_two_trailing,
|
||||
=> {
|
||||
var buf: [2]Ast.Node.Index = undefined;
|
||||
return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
|
||||
},
|
||||
|
||||
.error_set_decl => {
|
||||
const error_token = main_tokens[node];
|
||||
const lbrace = error_token + 1;
|
||||
const rbrace = datas[node].rhs;
|
||||
|
||||
var i = lbrace + 1;
|
||||
while (i < rbrace) : (i += 1) {
|
||||
switch (token_tags[i]) {
|
||||
.doc_comment => unreachable, // TODO
|
||||
.identifier => try walkIdentifier(w, i),
|
||||
.comma => {},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.builtin_call_two, .builtin_call_two_comma => {
|
||||
if (datas[node].lhs == 0) {
|
||||
return walkBuiltinCall(w, main_tokens[node], &.{});
|
||||
} else if (datas[node].rhs == 0) {
|
||||
return walkBuiltinCall(w, main_tokens[node], &.{datas[node].lhs});
|
||||
} else {
|
||||
return walkBuiltinCall(w, main_tokens[node], &.{ datas[node].lhs, datas[node].rhs });
|
||||
}
|
||||
},
|
||||
.builtin_call, .builtin_call_comma => {
|
||||
const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
|
||||
return walkBuiltinCall(w, main_tokens[node], params);
|
||||
},
|
||||
|
||||
.fn_proto_simple,
|
||||
.fn_proto_multi,
|
||||
.fn_proto_one,
|
||||
.fn_proto,
|
||||
=> {
|
||||
var buf: [1]Ast.Node.Index = undefined;
|
||||
return walkFnProto(w, ast.fullFnProto(&buf, node).?);
|
||||
},
|
||||
|
||||
.anyframe_type => {
|
||||
if (datas[node].rhs != 0) {
|
||||
return walkExpression(w, datas[node].rhs);
|
||||
}
|
||||
},
|
||||
|
||||
.@"switch",
|
||||
.switch_comma,
|
||||
=> {
|
||||
const condition = datas[node].lhs;
|
||||
const extra = ast.extraData(datas[node].rhs, Ast.Node.SubRange);
|
||||
const cases = ast.extra_data[extra.start..extra.end];
|
||||
|
||||
try walkExpression(w, condition); // condition expression
|
||||
try walkExpressions(w, cases);
|
||||
},
|
||||
|
||||
.switch_case_one,
|
||||
.switch_case_inline_one,
|
||||
.switch_case,
|
||||
.switch_case_inline,
|
||||
=> return walkSwitchCase(w, ast.fullSwitchCase(node).?),
|
||||
|
||||
.while_simple,
|
||||
.while_cont,
|
||||
.@"while",
|
||||
=> return walkWhile(w, ast.fullWhile(node).?),
|
||||
|
||||
.for_simple,
|
||||
.@"for",
|
||||
=> return walkFor(w, ast.fullFor(node).?),
|
||||
|
||||
.if_simple,
|
||||
.@"if",
|
||||
=> return walkIf(w, ast.fullIf(node).?),
|
||||
|
||||
.asm_simple,
|
||||
.@"asm",
|
||||
=> return walkAsm(w, ast.fullAsm(node).?),
|
||||
|
||||
.enum_literal => {
|
||||
return walkIdentifier(w, main_tokens[node]); // name
|
||||
},
|
||||
|
||||
.fn_decl => unreachable,
|
||||
.container_field => unreachable,
|
||||
.container_field_init => unreachable,
|
||||
.container_field_align => unreachable,
|
||||
.root => unreachable,
|
||||
.global_var_decl => unreachable,
|
||||
.local_var_decl => unreachable,
|
||||
.simple_var_decl => unreachable,
|
||||
.aligned_var_decl => unreachable,
|
||||
.@"usingnamespace" => unreachable,
|
||||
.test_decl => unreachable,
|
||||
.asm_output => unreachable,
|
||||
.asm_input => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void {
|
||||
_ = decl_node;
|
||||
|
||||
if (var_decl.ast.type_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.type_node);
|
||||
}
|
||||
|
||||
if (var_decl.ast.align_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.align_node);
|
||||
}
|
||||
|
||||
if (var_decl.ast.addrspace_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.addrspace_node);
|
||||
}
|
||||
|
||||
if (var_decl.ast.section_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.section_node);
|
||||
}
|
||||
|
||||
assert(var_decl.ast.init_node != 0);
|
||||
|
||||
return walkExpression(w, var_decl.ast.init_node);
|
||||
}
|
||||
|
||||
fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
|
||||
try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name
|
||||
|
||||
if (var_decl.ast.type_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.type_node);
|
||||
}
|
||||
|
||||
if (var_decl.ast.align_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.align_node);
|
||||
}
|
||||
|
||||
if (var_decl.ast.addrspace_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.addrspace_node);
|
||||
}
|
||||
|
||||
if (var_decl.ast.section_node != 0) {
|
||||
try walkExpression(w, var_decl.ast.section_node);
|
||||
}
|
||||
|
||||
assert(var_decl.ast.init_node != 0);
|
||||
if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
|
||||
try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
|
||||
}
|
||||
|
||||
return walkExpression(w, var_decl.ast.init_node);
|
||||
}
|
||||
|
||||
fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
|
||||
if (field.ast.type_expr != 0) {
|
||||
try walkExpression(w, field.ast.type_expr); // type
|
||||
}
|
||||
if (field.ast.align_expr != 0) {
|
||||
try walkExpression(w, field.ast.align_expr); // alignment
|
||||
}
|
||||
try walkExpression(w, field.ast.value_expr); // value
|
||||
}
|
||||
|
||||
fn walkBlock(
|
||||
w: *Walk,
|
||||
block_node: Ast.Node.Index,
|
||||
statements: []const Ast.Node.Index,
|
||||
) Error!void {
|
||||
_ = block_node;
|
||||
const ast = w.ast;
|
||||
const node_tags = ast.nodes.items(.tag);
|
||||
|
||||
for (statements) |stmt| {
|
||||
switch (node_tags[stmt]) {
|
||||
.global_var_decl,
|
||||
.local_var_decl,
|
||||
.simple_var_decl,
|
||||
.aligned_var_decl,
|
||||
=> try walkLocalVarDecl(w, ast.fullVarDecl(stmt).?),
|
||||
|
||||
else => try walkExpression(w, stmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
|
||||
try walkExpression(w, array_type.ast.elem_count);
|
||||
if (array_type.ast.sentinel != 0) {
|
||||
try walkExpression(w, array_type.ast.sentinel);
|
||||
}
|
||||
return walkExpression(w, array_type.ast.elem_type);
|
||||
}
|
||||
|
||||
fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
|
||||
if (array_init.ast.type_expr != 0) {
|
||||
try walkExpression(w, array_init.ast.type_expr); // T
|
||||
}
|
||||
for (array_init.ast.elements) |elem_init| {
|
||||
try walkExpression(w, elem_init);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkStructInit(
|
||||
w: *Walk,
|
||||
struct_node: Ast.Node.Index,
|
||||
struct_init: Ast.full.StructInit,
|
||||
) Error!void {
|
||||
_ = struct_node;
|
||||
if (struct_init.ast.type_expr != 0) {
|
||||
try walkExpression(w, struct_init.ast.type_expr); // T
|
||||
}
|
||||
for (struct_init.ast.fields) |field_init| {
|
||||
try walkExpression(w, field_init);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
|
||||
try walkExpression(w, call.ast.fn_expr);
|
||||
try walkParamList(w, call.ast.params);
|
||||
}
|
||||
|
||||
fn walkSlice(
|
||||
w: *Walk,
|
||||
slice_node: Ast.Node.Index,
|
||||
slice: Ast.full.Slice,
|
||||
) Error!void {
|
||||
_ = slice_node;
|
||||
try walkExpression(w, slice.ast.sliced);
|
||||
try walkExpression(w, slice.ast.start);
|
||||
if (slice.ast.end != 0) {
|
||||
try walkExpression(w, slice.ast.end);
|
||||
}
|
||||
if (slice.ast.sentinel != 0) {
|
||||
try walkExpression(w, slice.ast.sentinel);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
|
||||
const ast = w.ast;
|
||||
const token_tags = ast.tokens.items(.tag);
|
||||
assert(token_tags[name_ident] == .identifier);
|
||||
const name_bytes = ast.tokenSlice(name_ident);
|
||||
_ = w.unreferenced_globals.swapRemove(name_bytes);
|
||||
}
|
||||
|
||||
fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
|
||||
_ = w;
|
||||
_ = name_ident;
|
||||
}
|
||||
|
||||
fn walkContainerDecl(
|
||||
w: *Walk,
|
||||
container_decl_node: Ast.Node.Index,
|
||||
container_decl: Ast.full.ContainerDecl,
|
||||
) Error!void {
|
||||
_ = container_decl_node;
|
||||
if (container_decl.ast.arg != 0) {
|
||||
try walkExpression(w, container_decl.ast.arg);
|
||||
}
|
||||
try walkMembers(w, container_decl.ast.members);
|
||||
}
|
||||
|
||||
fn walkBuiltinCall(
|
||||
w: *Walk,
|
||||
builtin_token: Ast.TokenIndex,
|
||||
params: []const Ast.Node.Index,
|
||||
) Error!void {
|
||||
_ = builtin_token;
|
||||
for (params) |param_node| {
|
||||
try walkExpression(w, param_node);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
|
||||
const ast = w.ast;
|
||||
|
||||
{
|
||||
var it = fn_proto.iterate(ast);
|
||||
while (it.next()) |param| {
|
||||
if (param.type_expr != 0) {
|
||||
try walkExpression(w, param.type_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fn_proto.ast.align_expr != 0) {
|
||||
try walkExpression(w, fn_proto.ast.align_expr);
|
||||
}
|
||||
|
||||
if (fn_proto.ast.addrspace_expr != 0) {
|
||||
try walkExpression(w, fn_proto.ast.addrspace_expr);
|
||||
}
|
||||
|
||||
if (fn_proto.ast.section_expr != 0) {
|
||||
try walkExpression(w, fn_proto.ast.section_expr);
|
||||
}
|
||||
|
||||
if (fn_proto.ast.callconv_expr != 0) {
|
||||
try walkExpression(w, fn_proto.ast.callconv_expr);
|
||||
}
|
||||
|
||||
try walkExpression(w, fn_proto.ast.return_type);
|
||||
}
|
||||
|
||||
fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
|
||||
for (expressions) |expression| {
|
||||
try walkExpression(w, expression);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
|
||||
for (switch_case.ast.values) |value_expr| {
|
||||
try walkExpression(w, value_expr);
|
||||
}
|
||||
try walkExpression(w, switch_case.ast.target_expr);
|
||||
}
|
||||
|
||||
fn walkWhile(w: *Walk, while_node: Ast.full.While) Error!void {
|
||||
try walkExpression(w, while_node.ast.cond_expr); // condition
|
||||
|
||||
if (while_node.ast.cont_expr != 0) {
|
||||
try walkExpression(w, while_node.ast.cont_expr);
|
||||
}
|
||||
|
||||
try walkExpression(w, while_node.ast.cond_expr); // condition
|
||||
|
||||
if (while_node.ast.then_expr != 0) {
|
||||
try walkExpression(w, while_node.ast.then_expr);
|
||||
}
|
||||
if (while_node.ast.else_expr != 0) {
|
||||
try walkExpression(w, while_node.ast.else_expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
|
||||
try walkParamList(w, for_node.ast.inputs);
|
||||
if (for_node.ast.then_expr != 0) {
|
||||
try walkExpression(w, for_node.ast.then_expr);
|
||||
}
|
||||
if (for_node.ast.else_expr != 0) {
|
||||
try walkExpression(w, for_node.ast.else_expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkIf(w: *Walk, if_node: Ast.full.If) Error!void {
|
||||
try walkExpression(w, if_node.ast.cond_expr); // condition
|
||||
|
||||
if (if_node.ast.then_expr != 0) {
|
||||
try walkExpression(w, if_node.ast.then_expr);
|
||||
}
|
||||
if (if_node.ast.else_expr != 0) {
|
||||
try walkExpression(w, if_node.ast.else_expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
|
||||
try walkExpression(w, asm_node.ast.template);
|
||||
for (asm_node.ast.items) |item| {
|
||||
try walkExpression(w, item);
|
||||
}
|
||||
}
|
||||
|
||||
fn walkParamList(w: *Walk, params: []const Ast.Node.Index) Error!void {
|
||||
for (params) |param_node| {
|
||||
try walkExpression(w, param_node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if it is already gutted (i.e. its body replaced with `@trap()`).
|
||||
fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
|
||||
// skip over discards
|
||||
const node_tags = ast.nodes.items(.tag);
|
||||
const datas = ast.nodes.items(.data);
|
||||
var statements_buf: [2]Ast.Node.Index = undefined;
|
||||
const statements = switch (node_tags[body_node]) {
|
||||
.block_two,
|
||||
.block_two_semicolon,
|
||||
=> blk: {
|
||||
statements_buf[0..2].* = .{ datas[body_node].lhs, datas[body_node].rhs };
|
||||
break :blk if (datas[body_node].lhs == 0)
|
||||
statements_buf[0..0]
|
||||
else if (datas[body_node].rhs == 0)
|
||||
statements_buf[0..1]
|
||||
else
|
||||
statements_buf[0..2];
|
||||
},
|
||||
|
||||
.block,
|
||||
.block_semicolon,
|
||||
=> ast.extra_data[datas[body_node].lhs..datas[body_node].rhs],
|
||||
|
||||
else => return false,
|
||||
};
|
||||
var i: usize = 0;
|
||||
while (i < statements.len) : (i += 1) {
|
||||
switch (categorizeStmt(ast, statements[i])) {
|
||||
.discard_identifier => continue,
|
||||
.trap_call => return i + 1 == statements.len,
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const StmtCategory = enum {
|
||||
discard_identifier,
|
||||
trap_call,
|
||||
other,
|
||||
};
|
||||
|
||||
fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
|
||||
const node_tags = ast.nodes.items(.tag);
|
||||
const datas = ast.nodes.items(.data);
|
||||
const main_tokens = ast.nodes.items(.main_token);
|
||||
switch (node_tags[stmt]) {
|
||||
.builtin_call_two, .builtin_call_two_comma => {
|
||||
if (datas[stmt].lhs == 0) {
|
||||
return categorizeBuiltinCall(ast, main_tokens[stmt], &.{});
|
||||
} else if (datas[stmt].rhs == 0) {
|
||||
return categorizeBuiltinCall(ast, main_tokens[stmt], &.{datas[stmt].lhs});
|
||||
} else {
|
||||
return categorizeBuiltinCall(ast, main_tokens[stmt], &.{ datas[stmt].lhs, datas[stmt].rhs });
|
||||
}
|
||||
},
|
||||
.builtin_call, .builtin_call_comma => {
|
||||
const params = ast.extra_data[datas[stmt].lhs..datas[stmt].rhs];
|
||||
return categorizeBuiltinCall(ast, main_tokens[stmt], params);
|
||||
},
|
||||
.assign => {
|
||||
const infix = datas[stmt];
|
||||
if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier)
|
||||
return .discard_identifier;
|
||||
return .other;
|
||||
},
|
||||
else => return .other,
|
||||
}
|
||||
}
|
||||
|
||||
fn categorizeBuiltinCall(
|
||||
ast: *const Ast,
|
||||
builtin_token: Ast.TokenIndex,
|
||||
params: []const Ast.Node.Index,
|
||||
) StmtCategory {
|
||||
if (params.len != 0) return .other;
|
||||
const name_bytes = ast.tokenSlice(builtin_token);
|
||||
if (std.mem.eql(u8, name_bytes, "@trap"))
|
||||
return .trap_call;
|
||||
return .other;
|
||||
}
|
||||
|
||||
fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
|
||||
const node_tags = ast.nodes.items(.tag);
|
||||
const main_tokens = ast.nodes.items(.main_token);
|
||||
switch (node_tags[node]) {
|
||||
.identifier => {
|
||||
const token_index = main_tokens[node];
|
||||
const name_bytes = ast.tokenSlice(token_index);
|
||||
return std.mem.eql(u8, name_bytes, "_");
|
||||
},
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
|
||||
const node_tags = ast.nodes.items(.tag);
|
||||
const main_tokens = ast.nodes.items(.main_token);
|
||||
switch (node_tags[node]) {
|
||||
.identifier => {
|
||||
const token_index = main_tokens[node];
|
||||
const name_bytes = ast.tokenSlice(token_index);
|
||||
return std.mem.eql(u8, name_bytes, "undefined");
|
||||
},
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
@ -14,3 +14,4 @@ pub const skip_non_native = false;
|
||||
pub const only_c = false;
|
||||
pub const force_gpa = false;
|
||||
pub const only_core_functionality = true;
|
||||
pub const only_reduce = false;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user