blob: 186d7e6956f4b09a8fb8535d6358c3aaac83f69a [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.
*/
#include <algorithm>
#include <iomanip>
#include <ir/module-utils.h>
#include <pass.h>
#include <support/colors.h>
#include <wasm-binary.h>
#include <wasm.h>
namespace wasm {
using Counts = std::map<const char*, int>;
static Counts lastCounts;
// Prints metrics between optimization passes.
struct Metrics
: public WalkerPass<PostWalker<Metrics, UnifiedExpressionVisitor<Metrics>>> {
bool modifiesBinaryenIR() override { return false; }
bool byFunction;
Counts counts;
Metrics(bool byFunction) : byFunction(byFunction) {}
void visitExpression(Expression* curr) {
auto name = getExpressionName(curr);
counts[name]++;
}
void doWalkModule(Module* module) {
std::string title = getArgumentOrDefault("metrics", "");
std::cout << "Metrics";
if (!title.empty()) {
std::cout << ": " << title;
}
std::cout << '\n';
ImportInfo imports(*module);
// global things
for (auto& curr : module->exports) {
visitExport(curr.get());
}
ModuleUtils::iterDefinedGlobals(*module,
[&](Global* curr) { walkGlobal(curr); });
// add imports / funcs / globals / exports / tables / memories
counts["[imports]"] = imports.getNumImports();
counts["[funcs]"] = imports.getNumDefinedFunctions();
counts["[globals]"] = imports.getNumDefinedGlobals();
counts["[tags]"] = imports.getNumDefinedTags();
counts["[exports]"] = module->exports.size();
counts["[tables]"] = imports.getNumDefinedTables();
counts["[memories]"] = imports.getNumDefinedMemories();
// add memory
for (auto& memory : module->memories) {
walkMemory(memory.get());
}
Index size = 0;
for (auto& segment : module->dataSegments) {
walkDataSegment(segment.get());
size += segment->data.size();
}
if (!module->memories.empty()) {
counts["[memory-data]"] = size;
}
// add table
size = 0;
for (auto& table : module->tables) {
walkTable(table.get());
}
for (auto& segment : module->elementSegments) {
walkElementSegment(segment.get());
size += segment->data.size();
}
if (!module->tables.empty()) {
counts["[table-data]"] = size;
}
if (byFunction) {
// print global
printCounts("global");
// compute binary info, so we know function sizes
BufferWithRandomAccess buffer;
WasmBinaryWriter writer(module, buffer, getPassOptions());
writer.write();
// print for each function
Index binaryIndex = 0;
ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) {
counts.clear();
walkFunction(func);
counts["[vars]"] = func->getNumVars();
counts["[binary-bytes]"] =
writer.tableOfContents.functionBodies[binaryIndex++].size;
printCounts(std::string("func: ") + func->name.toString());
});
// print for each export how much code size is due to it, i.e.,
// how much the module could shrink without it.
auto sizeAfterGlobalCleanup = [&](Module* module) {
PassRunner runner(module,
PassOptions::getWithDefaultOptimizationOptions());
runner.setIsNested(true);
runner.addDefaultGlobalOptimizationPostPasses(); // remove stuff
runner.run();
BufferWithRandomAccess buffer;
WasmBinaryWriter writer(module, buffer, getPassOptions());
writer.write();
return buffer.size();
};
size_t baseline;
{
Module test;
ModuleUtils::copyModule(*module, test);
baseline = sizeAfterGlobalCleanup(&test);
}
for (auto& exp : module->exports) {
// create a test module where we remove the export and then see how much
// can be removed thanks to that
Module test;
ModuleUtils::copyModule(*module, test);
test.removeExport(exp->name);
counts.clear();
counts["[removable-bytes-without-it]"] =
baseline - sizeAfterGlobalCleanup(&test);
printCounts(std::string("export: ") + exp->name.toString() + " (" +
exp->value.toString() + ')');
}
// check how much size depends on the start method
if (!module->start.isNull()) {
Module test;
ModuleUtils::copyModule(*module, test);
test.start = Name();
counts.clear();
counts["[removable-bytes-without-it]"] =
baseline - sizeAfterGlobalCleanup(&test);
printCounts(std::string("start: ") + module->start.toString());
}
// can't compare detailed info between passes yet
lastCounts.clear();
} else {
// add function info
size_t vars = 0;
ModuleUtils::iterDefinedFunctions(*module, [&](Function* func) {
walkFunction(func);
vars += func->getNumVars();
});
counts["[vars]"] = vars;
// print
printCounts("total");
// compare to next time
lastCounts = counts;
}
}
void printCounts(std::string title) {
using std::left;
using std::noshowpos;
using std::right;
using std::setw;
using std::showpos;
std::ostream& o = std::cout;
std::vector<const char*> keys;
// add total
int total = 0;
for (auto& [key, value] : counts) {
keys.push_back(key);
// total is of all the normal stuff, not the special [things]
if (key[0] != '[') {
total += value;
}
}
keys.push_back("[total]");
counts["[total]"] = total;
// sort
sort(keys.begin(), keys.end(), [](const char* a, const char* b) -> bool {
// Sort the [..] ones first.
if (a[0] == '[' && b[0] != '[') {
return true;
}
if (a[0] != '[' && b[0] == '[') {
return false;
}
return strcmp(b, a) > 0;
});
o << title << "\n";
for (auto* key : keys) {
auto value = counts[key];
if (value == 0 && key[0] != '[') {
continue;
}
o << " " << left << setw(15) << key << ": " << setw(8) << value;
if (lastCounts.count(key)) {
int before = lastCounts[key];
int after = value;
if (after - before) {
if (after > before) {
Colors::red(o);
} else {
Colors::green(o);
}
o << right << setw(8);
o << showpos << after - before << noshowpos;
Colors::normal(o);
}
}
o << "\n";
}
}
};
Pass* createMetricsPass() { return new Metrics(false); }
Pass* createFunctionMetricsPass() { return new Metrics(true); }
} // namespace wasm