Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions scripts/fuzz_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2865,6 +2865,9 @@ def get_random_opts():
# value (the same used in j2wasm atm)
if random.random() < 0.5:
ret += ['-pii=4']
# fuzz both with and without expecting later toolchain opts
if random.random() < 0.5:
ret += ['--expect-later-toolchain-opts']
# test both closed and open world
if CLOSED_WORLD:
ret += [CLOSED_WORLD_FLAG]
Expand Down
12 changes: 9 additions & 3 deletions src/ir/function-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace wasm::FunctionUtils {
// Checks if two functions are equal in all functional aspects,
// everything but their name (which can't be the same, in the same
// module!) - same params, vars, body, result, etc.
inline bool equal(Function* left, Function* right) {
inline bool equal(Function* left, Function* right, const PassOptions& options) {
if (left->type != right->type) {
return false;
}
Expand All @@ -44,10 +44,16 @@ inline bool equal(Function* left, Function* right) {
// only in metadata (following LLVM's example). If we have a non-optimization
// reason for comparing metadata here then we could add a flag for it. We do,
// however, compare metadata that changes semantics (not debug info, but
// things like jsCalled). We do that check conservatively, erasing the non-
// semantic-altering ones and then comparing (so that new annotations
// things like removableIfUnused).
auto leftAnnotations = Intrinsics::getAnnotations(left);
auto rightAnnotations = Intrinsics::getAnnotations(right);
if (options.lastOpts) {
// If these are the last toolchain opts, we do not need to worry about
// inhibiting others by merging a jsCalled function with a non-jsCalled one
// (after the merge, both will be annotated, preventing optimizations that
// would be a problem in the presence of JS calls).
leftAnnotations.jsCalled = rightAnnotations.jsCalled = false;
}
if (!leftAnnotations.equalOnSemanticsAltering(rightAnnotations)) {
return false;
}
Expand Down
5 changes: 5 additions & 0 deletions src/pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ struct PassOptions {
// that needs lowering, then we lower it, then we can optimize it again to the
// original form).
bool targetJS = false;
// Whether we are in the last major sequence of optimizations being run. If so
// then we can perform optimizations that only make sense to do at the very
// end (because they might inhibit further ones after). For example, if we run
// -O3 -O3 then in the second -O3 we might do more things.
bool lastOpts = false;
// Arbitrary string arguments from the commandline, which we forward to
// passes.
std::unordered_map<std::string, std::string> arguments;
Expand Down
7 changes: 5 additions & 2 deletions src/passes/DuplicateFunctionElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,13 @@ struct DuplicateFunctionElimination : public Pass {
if (duplicates.contains(second->name)) {
continue;
}
if (FunctionUtils::equal(first, second)) {
// great, we can replace the second with the first!
if (FunctionUtils::equal(first, second, options)) {
// Great, we can replace the second with the first!
replacements[second->name] = first->name;
duplicates.insert(second->name);
if (second->funcAnnotations.jsCalled) {
first->funcAnnotations.jsCalled = true;
}
}
}
}
Expand Down
59 changes: 58 additions & 1 deletion src/tools/optimization-options.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ struct OptimizationOptions : public ToolOptions {

std::vector<PassInfo> passes;

// Normally we assume no wasm-opt or other toolchain opts happen after us,
// but the user can tell us otherwise, which inhibits some opts (ones that
// limit subsequent opts).
bool expectLaterToolchainOpts = false;

// Add a request to run all the default opt passes. They are run with the
// current opt and shrink levels specified, which are read from passOptions.
//
Expand Down Expand Up @@ -319,6 +324,15 @@ struct OptimizationOptions : public ToolOptions {
[this](Options*, const std::string&) {
passOptions.zeroFilledMemory = true;
})
.add("--expect-later-toolchain-opts",
"",
"Expect further invocations of wasm-opt later, avoiding "
"optimizations that might inhibit them",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
expectLaterToolchainOpts = true;
})
.add("--skip-pass",
"-sp",
"Skip a pass (do not run it)",
Expand Down Expand Up @@ -410,7 +424,45 @@ struct OptimizationOptions : public ToolOptions {
passRunner.clear();
};

for (auto& pass : passes) {
// If we do not expect later toolchain opts, then this invocation of

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add "TODO: We could add a --begin-last-opts flag if we needed more fine-grained control over this setting."

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, TODO added.

// wasm-opt is the last one, and the last invocation of the default opts is
// the last one. Find that invocation and mark lastOpts=true there, which
// will then remain true until the end.
//
// That is, imagine we are called with this:
//
// wasm-opt --dce -O3 --rse -Oz --licm
// ^^
// Then lastOpts is set here --^^
// And it applies from then on: [.......]
// That is, we mark lastOpts on the -Oz (after --dce -O3 --rse) and it
// applies to the -Oz and the --licm.
//
// The idea is that the last -O3/-Oz/etc. is the last big cycle of opts, and
// we can assume it is the last (and other specific passes are assumed to be
// "last" if they are after it).
//
// TODO: We could maybe add a --begin-last-opts flag (whose position on the
// commandline matters) if we needed more fine-grained control here.
passRunner.options.lastOpts = false;
Index lastDefaultOptsIndex = Index(-1);
if (!expectLaterToolchainOpts) {
for (Index i = 0; i < passes.size(); ++i) {
if (passes[i].name == DEFAULT_OPT_PASSES) {
lastDefaultOptsIndex = i;
}
}

if (lastDefaultOptsIndex == Index(-1)) {
// No default opts run at all. Just mark lastOpts now, so it applies to
// the non-default opts (same as it would apply to all non-default opts
// after the last default ones).
passRunner.options.lastOpts = true;
}
}

for (Index i = 0; i < passes.size(); ++i) {
auto& pass = passes[i];
if (pass.name == DEFAULT_OPT_PASSES) {
// This is something like -O3 or -Oz. We must run this now, in order to
// set the proper opt and shrink levels. To do that, first reset the
Expand All @@ -428,6 +480,11 @@ struct OptimizationOptions : public ToolOptions {
passRunner.options.optimizeLevel = *pass.optimizeLevel;
passRunner.options.shrinkLevel = *pass.shrinkLevel;

// If these are the last default opts, mark that.
if (i == lastDefaultOptsIndex) {
passRunner.options.lastOpts = true;
}

// Run our optimizations now with the custom levels.
passRunner.addDefaultOptimizationPasses();
flush();
Expand Down
5 changes: 5 additions & 0 deletions test/lit/help/wasm-metadce.test
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,11 @@
;; CHECK-NEXT: --zero-filled-memory,-uim Assume that an imported memory
;; CHECK-NEXT: will be zero-initialized
;; CHECK-EMPTY:
;; CHECK-NEXT: --expect-later-toolchain-opts Expect further invocations of
;; CHECK-NEXT: wasm-opt later, avoiding
;; CHECK-NEXT: optimizations that might inhibit
;; CHECK-NEXT: them
;; CHECK-EMPTY:
;; CHECK-NEXT: --skip-pass,-sp Skip a pass (do not run it)
;; CHECK-EMPTY:
;; CHECK-EMPTY:
Expand Down
5 changes: 5 additions & 0 deletions test/lit/help/wasm-opt.test
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,11 @@
;; CHECK-NEXT: --zero-filled-memory,-uim Assume that an imported memory
;; CHECK-NEXT: will be zero-initialized
;; CHECK-EMPTY:
;; CHECK-NEXT: --expect-later-toolchain-opts Expect further invocations of
;; CHECK-NEXT: wasm-opt later, avoiding
;; CHECK-NEXT: optimizations that might inhibit
;; CHECK-NEXT: them
;; CHECK-EMPTY:
;; CHECK-NEXT: --skip-pass,-sp Skip a pass (do not run it)
;; CHECK-EMPTY:
;; CHECK-EMPTY:
Expand Down
5 changes: 5 additions & 0 deletions test/lit/help/wasm2js.test
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,11 @@
;; CHECK-NEXT: --zero-filled-memory,-uim Assume that an imported memory
;; CHECK-NEXT: will be zero-initialized
;; CHECK-EMPTY:
;; CHECK-NEXT: --expect-later-toolchain-opts Expect further invocations of
;; CHECK-NEXT: wasm-opt later, avoiding
;; CHECK-NEXT: optimizations that might inhibit
;; CHECK-NEXT: them
;; CHECK-EMPTY:
;; CHECK-NEXT: --skip-pass,-sp Skip a pass (do not run it)
;; CHECK-EMPTY:
;; CHECK-EMPTY:
Expand Down
Loading
Loading