#include "instrumenter.h"
#include "ir/module-utils.h"
#include "ir/names.h"
#include "support/name.h"
#include "wasm-builder.h"
#include "wasm-type.h"
namespace wasm {
Instrumenter::Instrumenter(const InstrumenterConfig& config,
uint64_t moduleHash)
: config(config), moduleHash(moduleHash) {}
void Instrumenter::run(Module* wasm) {
this->wasm = wasm;
size_t numFuncs = 0;
ModuleUtils::iterDefinedFunctions(*wasm, [&](Function*) { ++numFuncs; });
addGlobals(numFuncs);
addSecondaryMemory(numFuncs);
instrumentFuncs();
addProfileExport(numFuncs);
}
void Instrumenter::addGlobals(size_t numFuncs) {
if (config.storageKind != WasmSplitOptions::StorageKind::InGlobals) {
return;
}
counterGlobal = Names::getValidGlobalName(*wasm, "monotonic_counter");
functionGlobals.reserve(numFuncs);
ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
functionGlobals.push_back(
Names::getValidGlobalName(*wasm, func->name.toString() + "_timestamp"));
});
auto addGlobal = [&](Name name) {
auto global = Builder::makeGlobal(
name,
Type::i32,
Builder(*wasm).makeConst(Literal::makeZero(Type::i32)),
Builder::Mutable);
global->hasExplicitName = true;
wasm->addGlobal(std::move(global));
};
addGlobal(counterGlobal);
for (auto& name : functionGlobals) {
addGlobal(name);
}
}
void Instrumenter::addSecondaryMemory(size_t numFuncs) {
if (config.storageKind != WasmSplitOptions::StorageKind::InSecondaryMemory) {
return;
}
if (!wasm->features.hasMultiMemory()) {
Fatal()
<< "error: --in-secondary-memory requires multimemory to be enabled";
}
secondaryMemory =
Names::getValidMemoryName(*wasm, config.secondaryMemoryName);
size_t pages = (numFuncs + Memory::kPageSize - 1) / Memory::kPageSize;
auto mem = Builder::makeMemory(secondaryMemory, pages, pages, true);
mem->module = config.importNamespace;
mem->base = config.secondaryMemoryName;
wasm->addMemory(std::move(mem));
}
void Instrumenter::instrumentFuncs() {
Builder builder(*wasm);
switch (config.storageKind) {
case WasmSplitOptions::StorageKind::InGlobals: {
auto globalIt = functionGlobals.begin();
ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
func->body = builder.makeSequence(
builder.makeIf(
builder.makeUnary(EqZInt32,
builder.makeGlobalGet(*globalIt, Type::i32)),
builder.makeSequence(
builder.makeGlobalSet(
counterGlobal,
builder.makeBinary(
AddInt32,
builder.makeGlobalGet(counterGlobal, Type::i32),
builder.makeConst(Literal::makeOne(Type::i32)))),
builder.makeGlobalSet(
*globalIt, builder.makeGlobalGet(counterGlobal, Type::i32)))),
func->body,
func->body->type);
++globalIt;
});
break;
}
case WasmSplitOptions::StorageKind::InMemory:
case WasmSplitOptions::StorageKind::InSecondaryMemory: {
if (!wasm->features.hasAtomics()) {
const char* command =
config.storageKind == WasmSplitOptions::StorageKind::InMemory
? "in-memory"
: "in-secondary-memory";
Fatal() << "error: --" << command << " requires atomics to be enabled";
}
Index funcIdx = 0;
assert(!wasm->memories.empty());
Name memoryName =
config.storageKind == WasmSplitOptions::StorageKind::InMemory
? wasm->memories[0]->name
: secondaryMemory;
ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) {
func->body = builder.makeSequence(
builder.makeAtomicStore(1,
funcIdx,
builder.makeConstPtr(0, Type::i32),
builder.makeConst(uint32_t(1)),
Type::i32,
memoryName),
func->body,
func->body->type);
++funcIdx;
});
break;
}
}
}
void Instrumenter::addProfileExport(size_t numFuncs) {
const size_t profileSize = 8 + 4 * numFuncs;
size_t pages = (profileSize + Memory::kPageSize - 1) / Memory::kPageSize;
if (wasm->memories.empty()) {
wasm->addMemory(Builder::makeMemory("0"));
wasm->memories[0]->initial = pages;
wasm->memories[0]->max = pages;
} else if (wasm->memories[0]->initial < pages) {
wasm->memories[0]->initial = pages;
if (wasm->memories[0]->max < pages) {
wasm->memories[0]->max = pages;
}
}
auto ptrType = wasm->memories[0]->indexType;
auto name = Names::getValidFunctionName(*wasm, config.profileExport);
auto writeProfile =
Builder::makeFunction(name, Signature({ptrType, Type::i32}, Type::i32), {});
writeProfile->hasExplicitName = true;
writeProfile->setLocalName(0, "addr");
writeProfile->setLocalName(1, "size");
Builder builder(*wasm);
auto getAddr = [&]() { return builder.makeLocalGet(0, ptrType); };
auto getSize = [&]() { return builder.makeLocalGet(1, Type::i32); };
auto hashConst = [&]() { return builder.makeConst(int64_t(moduleHash)); };
auto profileSizeConst = [&]() {
return builder.makeConst(int32_t(profileSize));
};
Expression* writeData = builder.makeStore(
8, 0, 1, getAddr(), hashConst(), Type::i64, wasm->memories[0]->name);
uint32_t offset = 8;
switch (config.storageKind) {
case WasmSplitOptions::StorageKind::InGlobals: {
for (const auto& global : functionGlobals) {
writeData = builder.blockify(
writeData,
builder.makeStore(4,
offset,
1,
getAddr(),
builder.makeGlobalGet(global, Type::i32),
Type::i32,
wasm->memories[0]->name));
offset += 4;
}
break;
}
case WasmSplitOptions::StorageKind::InMemory:
case WasmSplitOptions::StorageKind::InSecondaryMemory: {
Index funcIdxVar =
Builder::addVar(writeProfile.get(), "funcIdx", Type::i32);
auto getFuncIdx = [&]() {
return builder.makeLocalGet(funcIdxVar, Type::i32);
};
Name loadMemoryName =
config.storageKind == WasmSplitOptions::StorageKind::InMemory
? wasm->memories[0]->name
: secondaryMemory;
writeData = builder.blockify(
writeData,
builder.makeBlock(
"outer",
builder.makeLoop(
"l",
builder.blockify(
builder.makeBreak(
"outer",
nullptr,
builder.makeBinary(EqInt32,
getFuncIdx(),
builder.makeConst(uint32_t(numFuncs)))),
builder.makeStore(
4,
offset,
4,
builder.makeBinary(
AddInt32,
getAddr(),
builder.makeBinary(
MulInt32, getFuncIdx(), builder.makeConst(uint32_t(4)))),
builder.makeAtomicLoad(
1, 0, getFuncIdx(), Type::i32, loadMemoryName),
Type::i32,
wasm->memories[0]->name),
builder.makeLocalSet(
funcIdxVar,
builder.makeBinary(
AddInt32, getFuncIdx(), builder.makeConst(uint32_t(1)))),
builder.makeBreak("l")))));
break;
}
}
writeProfile->body = builder.makeSequence(
builder.makeIf(builder.makeBinary(GeUInt32, getSize(), profileSizeConst()),
writeData),
profileSizeConst());
wasm->addFunction(std::move(writeProfile));
wasm->addExport(
Builder::makeExport(config.profileExport, name, ExternalKind::Function));
if (!wasm->memories[0]->imported()) {
bool memoryExported = false;
for (auto& ex : wasm->exports) {
if (ex->kind == ExternalKind::Memory) {
memoryExported = true;
break;
}
}
if (!memoryExported) {
wasm->addExport(Builder::makeExport(
"profile-memory",
Names::getValidExportName(*wasm, wasm->memories[0]->name),
ExternalKind::Memory));
}
}
}
}