blob: 333380d049039037656f47d9d1d2f74cf65fafa4 [file] [log] [blame] [edit]
/*
* Copyright 2016 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasm_tools_optimization_options_h
#define wasm_tools_optimization_options_h
#include "tool-options.h"
//
// Shared optimization options for commandline tools
//
namespace wasm {
struct OptimizationOptions : public ToolOptions {
// By default we allow StackIR and enable it by default in higher optimization
// levels, but users can disallow it as well.
bool allowStackIR = true;
void parse(int argc, const char* argv[]) {
ToolOptions::parse(argc, argv);
// After parsing the arguments, update defaults based on the optimize/shrink
// levels.
if (allowStackIR &&
(passOptions.optimizeLevel >= 2 || passOptions.shrinkLevel >= 1)) {
passOptions.generateStackIR = true;
passOptions.optimizeStackIR = true;
}
}
static constexpr const char* DEFAULT_OPT_PASSES = "O";
static constexpr const int OS_OPTIMIZE_LEVEL = 2;
static constexpr const int OS_SHRINK_LEVEL = 1;
// Information to run a pass, as requested by a commandline flag.
struct PassInfo {
// The name of the pass to run.
std::string name;
// The main argument of the pass, if applicable.
std::optional<std::string> argument;
// The optimize and shrink levels to run the pass with, if specified. If not
// specified then the defaults are used.
std::optional<int> optimizeLevel;
std::optional<int> shrinkLevel;
PassInfo(std::string name) : name(name) {}
PassInfo(const char* name) : name(name) {}
PassInfo(std::string name, int optimizeLevel, int shrinkLevel)
: name(name), optimizeLevel(optimizeLevel), shrinkLevel(shrinkLevel) {}
};
std::vector<PassInfo> passes;
// 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.
//
// Each caller to here sets the opt and shrink levels before, which provides
// the right values for us to read. That is, -Os etc. sets the default opt
// level, so that the last of -O3 -Os will override the previous default, but
// also we note the current opt level for when we run the pass, so that the
// sequence -O3 -Os will run -O3 and then -Os, and not -Os twice.
void addDefaultOptPasses() {
passes.push_back(PassInfo{
DEFAULT_OPT_PASSES, passOptions.optimizeLevel, passOptions.shrinkLevel});
}
constexpr static const char* OptimizationOptionsCategory =
"Optimization options";
OptimizationOptions(const std::string& command,
const std::string& description)
: ToolOptions(command, description) {
(*this)
.add(
"",
"-O",
"execute default optimization passes (equivalent to -Os)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.setDefaultOptimizationOptions();
static_assert(
PassOptions::DEFAULT_OPTIMIZE_LEVEL == OS_OPTIMIZE_LEVEL &&
PassOptions::DEFAULT_SHRINK_LEVEL == OS_SHRINK_LEVEL,
"Help text states that -O is equivalent to -Os but now it isn't.");
addDefaultOptPasses();
})
.add("",
"-O0",
"execute no optimization passes",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.optimizeLevel = 0;
passOptions.shrinkLevel = 0;
})
.add("",
"-O1",
"execute -O1 optimization passes (quick&useful opts, useful for "
"iteration builds)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.optimizeLevel = 1;
passOptions.shrinkLevel = 0;
addDefaultOptPasses();
})
.add(
"",
"-O2",
"execute -O2 optimization passes (most opts, generally gets most perf)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.optimizeLevel = 2;
passOptions.shrinkLevel = 0;
addDefaultOptPasses();
})
.add("",
"-O3",
"execute -O3 optimization passes (spends potentially a lot of time "
"optimizing)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.optimizeLevel = 3;
passOptions.shrinkLevel = 0;
addDefaultOptPasses();
})
.add("",
"-O4",
"execute -O4 optimization passes (also flatten the IR, which can "
"take a lot more time and memory, but is useful on more nested / "
"complex / less-optimized input)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.optimizeLevel = 4;
passOptions.shrinkLevel = 0;
addDefaultOptPasses();
})
.add("",
"-Os",
"execute default optimization passes, focusing on code size",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.optimizeLevel = OS_OPTIMIZE_LEVEL;
passOptions.shrinkLevel = OS_SHRINK_LEVEL;
addDefaultOptPasses();
})
.add("",
"-Oz",
"execute default optimization passes, super-focusing on code size",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.optimizeLevel = 2;
passOptions.shrinkLevel = 2;
addDefaultOptPasses();
})
.add("--optimize-level",
"-ol",
"How much to focus on optimizing code",
OptimizationOptionsCategory,
Options::Arguments::One,
[this](Options* o, const std::string& argument) {
passOptions.optimizeLevel = atoi(argument.c_str());
})
.add("--shrink-level",
"-s",
"How much to focus on shrinking code size",
OptimizationOptionsCategory,
Options::Arguments::One,
[this](Options* o, const std::string& argument) {
passOptions.shrinkLevel = atoi(argument.c_str());
})
.add("--debuginfo",
"-g",
"Emit names section in wasm binary (or full debuginfo in wast)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[&](Options* o, const std::string& arguments) {
passOptions.debugInfo = true;
})
.add("--no-stack-ir",
"",
"do not use StackIR (even when it is the default)",
ToolOptionsCategory,
Options::Arguments::Zero,
[&](Options* o, const std::string& arguments) {
allowStackIR = false;
passOptions.generateStackIR = false;
passOptions.optimizeStackIR = false;
})
.add("--always-inline-max-function-size",
"-aimfs",
"Max size of functions that are always inlined (default " +
std::to_string(InliningOptions().alwaysInlineMaxSize) +
", which "
"is safe for use with -Os builds)",
OptimizationOptionsCategory,
Options::Arguments::One,
[this](Options* o, const std::string& argument) {
passOptions.inlining.alwaysInlineMaxSize =
static_cast<Index>(atoi(argument.c_str()));
})
.add("--flexible-inline-max-function-size",
"-fimfs",
"Max size of functions that are inlined when lightweight (no loops "
"or function calls) when optimizing aggressively for speed (-O3). "
"Default: " +
std::to_string(InliningOptions().flexibleInlineMaxSize),
OptimizationOptionsCategory,
Options::Arguments::One,
[this](Options* o, const std::string& argument) {
passOptions.inlining.flexibleInlineMaxSize =
static_cast<Index>(atoi(argument.c_str()));
})
.add("--one-caller-inline-max-function-size",
"-ocimfs",
"Max size of functions that are inlined when there is only one "
"caller (default -1, which means all such functions are inlined)",
OptimizationOptionsCategory,
Options::Arguments::One,
[this](Options* o, const std::string& argument) {
static_assert(InliningOptions().oneCallerInlineMaxSize ==
Index(-1),
"the help text here is written to assume -1");
passOptions.inlining.oneCallerInlineMaxSize =
static_cast<Index>(atoi(argument.c_str()));
})
.add("--inline-functions-with-loops",
"-ifwl",
"Allow inlining functions with loops",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options* o, const std::string&) {
passOptions.inlining.allowFunctionsWithLoops = true;
})
.add("--partial-inlining-ifs",
"-pii",
"Number of ifs allowed in partial inlining (zero means partial "
"inlining is disabled) (default: " +
std::to_string(InliningOptions().partialInliningIfs) + ')',
OptimizationOptionsCategory,
Options::Arguments::One,
[this](Options* o, const std::string& argument) {
passOptions.inlining.partialInliningIfs =
static_cast<Index>(std::stoi(argument));
})
.add("--ignore-implicit-traps",
"-iit",
"Optimize under the helpful assumption that no surprising traps "
"occur (from load, div/mod, etc.)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.ignoreImplicitTraps = true;
})
.add("--traps-never-happen",
"-tnh",
"Optimize under the helpful assumption that no trap is reached at "
"runtime (from load, div/mod, etc.)",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.trapsNeverHappen = true;
})
.add("--low-memory-unused",
"-lmu",
"Optimize under the helpful assumption that the low 1K of memory is "
"not used by the application",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.lowMemoryUnused = true;
})
.add(
"--fast-math",
"-ffm",
"Optimize floats without handling corner cases of NaNs and rounding",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) { passOptions.fastMath = true; })
.add("--zero-filled-memory",
"-uim",
"Assume that an imported memory will be zero-initialized",
OptimizationOptionsCategory,
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.zeroFilledMemory = true;
})
.add("--skip-pass",
"-sp",
"Skip a pass (do not run it)",
OptimizationOptionsCategory,
Options::Arguments::N,
[this](Options*, const std::string& pass) {
passOptions.passesToSkip.insert(pass);
});
// add passes in registry
for (const auto& p : PassRegistry::get()->getRegisteredNames()) {
(*this).add(
std::string("--") + p,
"",
PassRegistry::get()->getPassDescription(p),
"Optimization passes",
// Allow an optional parameter to a pass. If provided, it is
// the same as if using --pass-arg, that is,
//
// --foo=ARG
//
// is the same as
//
// --foo --pass-arg=foo@ARG
Options::Arguments::Optional,
[this, p](Options*, const std::string& arg) {
PassInfo info(p);
if (!arg.empty()) {
info.argument = arg;
}
passes.push_back(info);
},
PassRegistry::get()->isPassHidden(p));
}
}
// Pass arguments with the same name as the pass are stored per-instance on
// PassInfo, while all other arguments are stored globally on
// passOptions.arguments (which is what the overriden method on ToolOptions
// does).
void addPassArg(const std::string& key, const std::string& value) override {
// Scan the current pass list for the last defined instance of a pass named
// like the argument under consideration.
for (auto i = passes.rbegin(); i != passes.rend(); i++) {
if (i->name != key) {
continue;
}
if (i->argument.has_value()) {
Fatal() << i->name << " already set to " << *(i->argument);
}
// Found? Store the argument value there and return.
i->argument = value;
return;
}
// Not found? Store it globally if there is no pass with the same name.
if (!PassRegistry::get()->containsPass(key)) {
return ToolOptions::addPassArg(key, value);
}
// Not found, but we have a pass with the same name? Bail out.
Fatal() << "can't set " << key << ": pass not enabled";
}
bool runningDefaultOptimizationPasses() {
for (auto& pass : passes) {
if (pass.name == DEFAULT_OPT_PASSES) {
return true;
}
}
return false;
}
bool runningPasses() { return passes.size() > 0; }
void runPasses(Module& wasm) {
PassRunner passRunner(&wasm, passOptions);
if (debug) {
passRunner.setDebug(true);
}
// Flush anything in the current pass runner, and then reset it to a fresh
// state so it is ready for new things.
auto flush = [&]() {
passRunner.run();
passRunner.clear();
};
for (auto& pass : passes) {
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
// runner so that anything already queued is run (since we can only run
// after those things).
flush();
// -O3/-Oz etc. always set their own optimize/shrinkLevels.
assert(pass.optimizeLevel);
assert(pass.shrinkLevel);
// Temporarily override the default levels.
assert(passRunner.options.optimizeLevel == passOptions.optimizeLevel);
assert(passRunner.options.shrinkLevel == passOptions.shrinkLevel);
passRunner.options.optimizeLevel = *pass.optimizeLevel;
passRunner.options.shrinkLevel = *pass.shrinkLevel;
// Run our optimizations now with the custom levels.
passRunner.addDefaultOptimizationPasses();
flush();
// Restore the default optimize/shrinkLevels.
passRunner.options.optimizeLevel = passOptions.optimizeLevel;
passRunner.options.shrinkLevel = passOptions.shrinkLevel;
} else {
// This is a normal pass. Add it to the queue for execution.
passRunner.add(pass.name, pass.argument);
// Normal passes do not set their own optimize/shrinkLevels.
assert(!pass.optimizeLevel);
assert(!pass.shrinkLevel);
}
}
flush();
}
};
} // namespace wasm
#endif