| /* |
| * 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. |
| */ |
| |
| // |
| // i64 values are not valid in JS, and must be handled in some other |
| // way. This pass transforms all i64s in params and results in imports |
| // and exports into pairs of i32, i32 (low, high). If JS on the outside |
| // calls with that ABI, then everything should then just work, using |
| // stub methods added in this pass, that thunk i64s into i32, i32 and |
| // vice versa as necessary. |
| // |
| // We can also legalize in a "minimal" way, that is, only JS-specific |
| // components, that only JS will care about, such as dynCall methods |
| // (wasm will never call them, as it can share the table directly). E.g. |
| // is dynamic linking, where we can avoid legalizing wasm=>wasm calls |
| // across modules, we still want to legalize dynCalls so JS can call into the |
| // table even to a signature that is not legal. |
| // |
| |
| #include "asm_v_wasm.h" |
| #include "asmjs/shared-constants.h" |
| #include "ir/import-utils.h" |
| #include "ir/literal-utils.h" |
| #include "ir/utils.h" |
| #include "pass.h" |
| #include "shared-constants.h" |
| #include "wasm-builder.h" |
| #include "wasm.h" |
| #include <utility> |
| |
| namespace wasm { |
| |
| struct LegalizeJSInterface : public Pass { |
| bool full; |
| |
| LegalizeJSInterface(bool full) : full(full) {} |
| |
| void run(PassRunner* runner, Module* module) override { |
| auto exportOriginals = |
| !runner->options |
| .getArgumentOrDefault("legalize-js-interface-export-originals", "") |
| .empty(); |
| // for each illegal export, we must export a legalized stub instead |
| std::vector<Export*> newExports; |
| for (auto& ex : module->exports) { |
| if (ex->kind == ExternalKind::Function) { |
| // if it's an import, ignore it |
| auto* func = module->getFunction(ex->value); |
| if (isIllegal(func) && shouldBeLegalized(ex.get(), func)) { |
| // Provide a legal function for the export. |
| auto legalName = makeLegalStub(func, module); |
| ex->value = legalName; |
| if (exportOriginals) { |
| // Also export the original function, before legalization. This is |
| // not normally useful for JS, except in cases like dynamic linking |
| // where the JS loader code must copy exported wasm functions into |
| // the table, and they must not be legalized as other wasm code will |
| // do an indirect call to them. However, don't do this for imported |
| // functions, as those would be legalized in their actual module |
| // anyhow. It also makes no sense to do this for dynCalls, as they |
| // are only called from JS. |
| if (!func->imported() && !isDynCall(ex->name)) { |
| Builder builder(*module); |
| Name newName = std::string("orig$") + ex->name.str; |
| newExports.push_back(builder.makeExport( |
| newName, func->name, ExternalKind::Function)); |
| } |
| } |
| } |
| } |
| } |
| for (auto* ex : newExports) { |
| module->addExport(ex); |
| } |
| // Avoid iterator invalidation later. |
| std::vector<Function*> originalFunctions; |
| for (auto& func : module->functions) { |
| originalFunctions.push_back(func.get()); |
| } |
| // for each illegal import, we must call a legalized stub instead |
| for (auto* im : originalFunctions) { |
| if (im->imported() && isIllegal(im) && shouldBeLegalized(im)) { |
| auto funcName = makeLegalStubForCalledImport(im, module); |
| illegalImportsToLegal[im->name] = funcName; |
| // we need to use the legalized version in the table, as the import from |
| // JS is legal for JS. Our stub makes it look like a native wasm |
| // function. |
| for (auto& segment : module->table.segments) { |
| for (auto& name : segment.data) { |
| if (name == im->name) { |
| name = funcName; |
| } |
| } |
| } |
| } |
| } |
| |
| if (!illegalImportsToLegal.empty()) { |
| // Gather functions used in 'ref.func'. They should not be removed. |
| std::unordered_map<Name, std::atomic<bool>> usedInRefFunc; |
| |
| // Fill in unordered_map, as we operate on it in parallel. |
| for (auto& func : module->functions) { |
| usedInRefFunc[func->name]; |
| } |
| |
| struct RefFuncScanner : public WalkerPass<PostWalker<RefFuncScanner>> { |
| Module& wasm; |
| std::unordered_map<Name, std::atomic<bool>>& usedInRefFunc; |
| |
| bool isFunctionParallel() override { return true; } |
| |
| Pass* create() override { |
| return new RefFuncScanner(wasm, usedInRefFunc); |
| } |
| |
| RefFuncScanner( |
| Module& wasm, |
| std::unordered_map<Name, std::atomic<bool>>& usedInRefFunc) |
| : wasm(wasm), usedInRefFunc(usedInRefFunc) {} |
| |
| void visitRefFunc(RefFunc* curr) { usedInRefFunc[curr->func] = true; } |
| }; |
| |
| RefFuncScanner(*module, usedInRefFunc).run(runner, module); |
| for (auto& pair : illegalImportsToLegal) { |
| if (!usedInRefFunc[pair.first]) { |
| module->removeFunction(pair.first); |
| } |
| } |
| |
| // fix up imports: call_import of an illegal must be turned to a call of a |
| // legal |
| struct FixImports : public WalkerPass<PostWalker<FixImports>> { |
| bool isFunctionParallel() override { return true; } |
| |
| Pass* create() override { |
| return new FixImports(illegalImportsToLegal); |
| } |
| |
| std::map<Name, Name>* illegalImportsToLegal; |
| |
| FixImports(std::map<Name, Name>* illegalImportsToLegal) |
| : illegalImportsToLegal(illegalImportsToLegal) {} |
| |
| void visitCall(Call* curr) { |
| auto iter = illegalImportsToLegal->find(curr->target); |
| if (iter == illegalImportsToLegal->end()) { |
| return; |
| } |
| |
| if (iter->second == getFunction()->name) { |
| // inside the stub function itself, is the one safe place to do the |
| // call |
| return; |
| } |
| replaceCurrent(Builder(*getModule()) |
| .makeCall(iter->second, curr->operands, curr->type)); |
| } |
| }; |
| |
| FixImports(&illegalImportsToLegal).run(runner, module); |
| } |
| } |
| |
| private: |
| // map of illegal to legal names for imports |
| std::map<Name, Name> illegalImportsToLegal; |
| |
| template<typename T> bool isIllegal(T* t) { |
| for (auto param : t->sig.params.expand()) { |
| if (param == Type::i64) { |
| return true; |
| } |
| } |
| return t->sig.results == Type::i64; |
| } |
| |
| bool isDynCall(Name name) { return name.startsWith("dynCall_"); } |
| |
| // Check if an export should be legalized. |
| bool shouldBeLegalized(Export* ex, Function* func) { |
| if (full) { |
| return true; |
| } |
| // We are doing minimal legalization - just what JS needs. |
| return isDynCall(ex->name); |
| } |
| |
| // Check if an import should be legalized. |
| bool shouldBeLegalized(Function* im) { |
| if (full) { |
| return true; |
| } |
| // We are doing minimal legalization - just what JS needs. |
| return im->module == ENV && im->base.startsWith("invoke_"); |
| } |
| |
| // JS calls the export, so it must call a legal stub that calls the actual |
| // wasm function |
| Name makeLegalStub(Function* func, Module* module) { |
| Builder builder(*module); |
| auto* legal = new Function(); |
| legal->name = Name(std::string("legalstub$") + func->name.str); |
| |
| auto* call = module->allocator.alloc<Call>(); |
| call->target = func->name; |
| call->type = func->sig.results; |
| |
| const std::vector<Type>& params = func->sig.params.expand(); |
| std::vector<Type> legalParams; |
| for (auto param : params) { |
| if (param == Type::i64) { |
| call->operands.push_back(I64Utilities::recreateI64( |
| builder, legalParams.size(), legalParams.size() + 1)); |
| legalParams.push_back(Type::i32); |
| legalParams.push_back(Type::i32); |
| } else { |
| call->operands.push_back( |
| builder.makeLocalGet(legalParams.size(), param)); |
| legalParams.push_back(param); |
| } |
| } |
| legal->sig.params = Type(legalParams); |
| |
| if (func->sig.results == Type::i64) { |
| Function* f = |
| getFunctionOrImport(module, SET_TEMP_RET0, Type::i32, Type::none); |
| legal->sig.results = Type::i32; |
| auto index = Builder::addVar(legal, Name(), Type::i64); |
| auto* block = builder.makeBlock(); |
| block->list.push_back(builder.makeLocalSet(index, call)); |
| block->list.push_back(builder.makeCall( |
| f->name, {I64Utilities::getI64High(builder, index)}, Type::none)); |
| block->list.push_back(I64Utilities::getI64Low(builder, index)); |
| block->finalize(); |
| legal->body = block; |
| } else { |
| legal->sig.results = func->sig.results; |
| legal->body = call; |
| } |
| |
| // a method may be exported multiple times |
| if (!module->getFunctionOrNull(legal->name)) { |
| module->addFunction(legal); |
| } |
| return legal->name; |
| } |
| |
| // wasm calls the import, so it must call a stub that calls the actual legal |
| // JS import |
| Name makeLegalStubForCalledImport(Function* im, Module* module) { |
| Builder builder(*module); |
| auto legalIm = make_unique<Function>(); |
| legalIm->name = Name(std::string("legalimport$") + im->name.str); |
| legalIm->module = im->module; |
| legalIm->base = im->base; |
| auto stub = make_unique<Function>(); |
| stub->name = Name(std::string("legalfunc$") + im->name.str); |
| stub->sig = im->sig; |
| |
| auto* call = module->allocator.alloc<Call>(); |
| call->target = legalIm->name; |
| |
| const std::vector<Type>& imParams = im->sig.params.expand(); |
| std::vector<Type> params; |
| for (size_t i = 0; i < imParams.size(); ++i) { |
| if (imParams[i] == Type::i64) { |
| call->operands.push_back(I64Utilities::getI64Low(builder, i)); |
| call->operands.push_back(I64Utilities::getI64High(builder, i)); |
| params.push_back(Type::i32); |
| params.push_back(Type::i32); |
| } else { |
| call->operands.push_back(builder.makeLocalGet(i, imParams[i])); |
| params.push_back(imParams[i]); |
| } |
| } |
| |
| if (im->sig.results == Type::i64) { |
| Function* f = |
| getFunctionOrImport(module, GET_TEMP_RET0, Type::none, Type::i32); |
| call->type = Type::i32; |
| Expression* get = builder.makeCall(f->name, {}, call->type); |
| stub->body = I64Utilities::recreateI64(builder, call, get); |
| } else { |
| call->type = im->sig.results; |
| stub->body = call; |
| } |
| legalIm->sig = Signature(Type(params), call->type); |
| |
| const auto& stubName = stub->name; |
| if (!module->getFunctionOrNull(stubName)) { |
| module->addFunction(std::move(stub)); |
| } |
| if (!module->getFunctionOrNull(legalIm->name)) { |
| module->addFunction(std::move(legalIm)); |
| } |
| return stubName; |
| } |
| |
| static Function* |
| getFunctionOrImport(Module* module, Name name, Type params, Type results) { |
| // First look for the function by name |
| if (Function* f = module->getFunctionOrNull(name)) { |
| return f; |
| } |
| // Then see if its already imported |
| ImportInfo info(*module); |
| if (Function* f = info.getImportedFunction(ENV, name)) { |
| return f; |
| } |
| // Failing that create a new function import. |
| auto import = new Function; |
| import->name = name; |
| import->module = ENV; |
| import->base = name; |
| import->sig = Signature(params, results); |
| module->addFunction(import); |
| return import; |
| } |
| }; |
| |
| Pass* createLegalizeJSInterfacePass() { return new LegalizeJSInterface(true); } |
| |
| Pass* createLegalizeJSInterfaceMinimallyPass() { |
| return new LegalizeJSInterface(false); |
| } |
| |
| } // namespace wasm |