blob: 5c6b70546a8e4b4e23d5dd949bc7d497c78606ea [file] [log] [blame] [edit]
/*
* Copyright 2024 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.
*/
//
// "Closes" the world, in the sense of making it more compatible with the
// --closed-world flag, in a potentially destructive manner. This is mainly
// useful for fuzzing (in that a random module is usually very incomptable with
// closed world, with most types being public and hence unoptimizable, but
// running this pass makes as many as we can fully private).
//
// The fixup we do is to find references sent out/received in, and to
// externalize / internalize them. For example, this export:
//
// (func $refs (export "refs") (param $x (ref $X)) (result (ref $Y))
//
// would have the following function exported in its place:
//
// (func $refs-closed (export "refs") (param $x externref) (result externref)
// (extern.convert_any
// (call $refs
// (ref.cast (ref $X)
// (any.convert_extern
// (local.get $x))))))
//
#include "ir/names.h"
#include "pass.h"
#include "wasm-builder.h"
#include "wasm.h"
namespace wasm {
namespace {
struct EncloseWorld : public Pass {
void run(Module* module) override {
// Handle exports.
// TODO: Non-function exports.
std::vector<std::unique_ptr<Export>> newExports;
for (auto& ex : module->exports) {
if (ex->kind == ExternalKind::Function) {
auto* func = module->getFunction(ex->value);
// If this opens up types, replace it with an enclosed stub.
if (opensTypes(func)) {
auto stubName = makeStubStubForExport(func, module);
ex->value = stubName;
}
}
}
for (auto& ex : newExports) {
module->addExport(std::move(ex));
}
// TODO: Handle imports.
}
private:
// Whether a type is an "open" ref, that is, a type that closed-world would
// consider to keep things public and prevent some amount of closed-world
// optimizations.
bool isOpenRef(Type t) {
// Only externref keeps things closed, and we must ignore things that
// cannot be converted to/from it (like funcrefs), so we can just check for
// the top type being any.
return t.isRef() && t.getHeapType().getTop() == HeapType::any;
}
// Whether a function causes types to be open.
bool opensTypes(Function* func) {
for (const auto& param : func->getParams()) {
if (isOpenRef(param)) {
return true;
}
}
// TODO: Handle tuple results.
return isOpenRef(func->getResults());
}
// Make an enclosed stub function for an exported function, and return its
// name.
Name makeStubStubForExport(Function* func, Module* module) {
// Pick a valid name for the stub we are about to create.
auto stubName = Names::getValidFunctionName(
*module, std::string("stub$") + func->name.toString());
// Create the stub.
Builder builder(*module);
// The stub's body is just a call to the original function, but with some
// conversions to/from externref.
std::vector<Expression*> params;
auto externref = Type(HeapType::ext, Nullable);
// Handle params.
std::vector<Type> stubParams;
for (const auto& param : func->getParams()) {
if (!isOpenRef(param)) {
// A normal parameter. Just pass it to the original function.
auto* get = builder.makeLocalGet(stubParams.size(), param);
params.push_back(get);
stubParams.push_back(param);
} else {
// A type we must fix up: receive as an externref and then internalize
// and cast before sending to the original function.
auto* get = builder.makeLocalGet(stubParams.size(), externref);
auto* interned = builder.makeRefAs(AnyConvertExtern, get);
// This cast may be trivial, but we leave it to the optimizer to remove.
auto* cast = builder.makeRefCast(interned, param);
params.push_back(cast);
stubParams.push_back(externref);
}
}
auto* call = builder.makeCall(func->name, params, func->getResults());
// Generate the stub's type.
auto oldResults = func->getResults();
Type resultsType = isOpenRef(oldResults) ? externref : oldResults;
auto type = Signature(Type(stubParams), resultsType);
// Handle the results and make the body.
Expression* body;
if (!isOpenRef(oldResults)) {
// Just use the call.
body = call;
} else {
// Fix up the call's result.
body = builder.makeRefAs(ExternConvertAny, call);
}
module->addFunction(builder.makeFunction(stubName, type, {}, body));
return stubName;
}
};
} // anonymous namespace
Pass* createEncloseWorldPass() { return new EncloseWorld(); }
} // namespace wasm