| /* | |
| * 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 incompatible 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* name = ex->getInternalName(); | |
| auto* func = module->getFunction(*name); | |
| // If this opens up types, replace it with an enclosed stub. | |
| if (opensTypes(func)) { | |
| auto stubName = makeStubStubForExport(func, module); | |
| *name = 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 |