| /* |
| * 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. |
| // |
| // This pass also legalizes according to asm.js FFI rules, which |
| // disallow f32s. TODO: an option to not do that, if it matters? |
| // |
| |
| #include <wasm.h> |
| #include <pass.h> |
| #include <wasm-builder.h> |
| #include <ast_utils.h> |
| |
| namespace wasm { |
| |
| Name TEMP_RET_0("tempRet0"); |
| |
| struct LegalizeJSInterface : public Pass { |
| void run(PassRunner* runner, Module* module) override { |
| // for each illegal export, we must export a legalized stub instead |
| for (auto& ex : module->exports) { |
| if (ex->kind == ExternalKind::Function) { |
| // if it's an import, ignore it |
| if (auto* func = module->checkFunction(ex->value)) { |
| if (isIllegal(func)) { |
| auto legalName = makeLegalStub(func, module); |
| ex->value = legalName; |
| } |
| } |
| } |
| } |
| // for each illegal import, we must call a legalized stub instead |
| std::vector<Import*> newImports; // add them at the end, to not invalidate the iter |
| for (auto& im : module->imports) { |
| if (im->kind == ExternalKind::Function && isIllegal(im->functionType)) { |
| Name funcName; |
| auto* legal = makeLegalStub(im.get(), module, funcName); |
| illegalToLegal[im->name] = funcName; |
| newImports.push_back(legal); |
| // 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 (illegalToLegal.size() > 0) { |
| for (auto* im : newImports) { |
| module->addImport(im); |
| } |
| |
| // fix up imports: call_import of an illegal must be turned to a call of a legal |
| |
| struct FixImports : public WalkerPass<PostWalker<FixImports, Visitor<FixImports>>> { |
| bool isFunctionParallel() override { return true; } |
| |
| Pass* create() override { return new FixImports(illegalToLegal); } |
| |
| std::map<Name, Name>* illegalToLegal; |
| |
| FixImports(std::map<Name, Name>* illegalToLegal) : illegalToLegal(illegalToLegal) {} |
| |
| void visitCallImport(CallImport* curr) { |
| auto iter = illegalToLegal->find(curr->target); |
| if (iter == illegalToLegal->end()) return; |
| |
| if (iter->second == getFunction()->name) return; // inside the stub function itself, is the one safe place to do the call |
| replaceCurrent(Builder(*getModule()).makeCall(iter->second, curr->operands, curr->type)); |
| } |
| }; |
| |
| PassRunner passRunner(module); |
| passRunner.add<FixImports>(&illegalToLegal); |
| passRunner.run(); |
| } |
| } |
| |
| private: |
| // map of illegal to legal names for imports |
| std::map<Name, Name> illegalToLegal; |
| |
| template<typename T> |
| bool isIllegal(T* t) { |
| for (auto param : t->params) { |
| if (param == i64 || param == f32) return true; |
| } |
| if (t->result == i64 || t->result == f32) return true; |
| return false; |
| } |
| |
| // 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->result; |
| |
| for (auto param : func->params) { |
| if (param == i64) { |
| call->operands.push_back(I64Utilities::recreateI64(builder, legal->params.size(), legal->params.size() + 1)); |
| legal->params.push_back(i32); |
| legal->params.push_back(i32); |
| } else if (param == f32) { |
| call->operands.push_back(builder.makeUnary(DemoteFloat64, builder.makeGetLocal(legal->params.size(), f64))); |
| legal->params.push_back(f64); |
| } else { |
| call->operands.push_back(builder.makeGetLocal(legal->params.size(), param)); |
| legal->params.push_back(param); |
| } |
| } |
| |
| if (func->result == i64) { |
| legal->result = i32; |
| auto index = builder.addVar(legal, Name(), i64); |
| auto* block = builder.makeBlock(); |
| block->list.push_back(builder.makeSetLocal(index, call)); |
| ensureTempRet0(module); |
| block->list.push_back(builder.makeSetGlobal( |
| TEMP_RET_0, |
| I64Utilities::getI64High(builder, index) |
| )); |
| block->list.push_back(I64Utilities::getI64Low(builder, index)); |
| block->finalize(); |
| legal->body = block; |
| } else if (func->result == f32) { |
| legal->result = f64; |
| legal->body = builder.makeUnary(PromoteFloat32, call); |
| } else { |
| legal->result = func->result; |
| legal->body = call; |
| } |
| |
| // a method may be exported multiple times |
| if (!module->checkFunction(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 |
| Import* makeLegalStub(Import* im, Module* module, Name& funcName) { |
| Builder builder(*module); |
| auto* type = new FunctionType(); |
| type->name = Name(std::string("legaltype$") + im->name.str); |
| auto* legal = new Import(); |
| legal->name = Name(std::string("legalimport$") + im->name.str); |
| legal->module = im->module; |
| legal->base = im->base; |
| legal->kind = ExternalKind::Function; |
| legal->functionType = type; |
| auto* func = new Function(); |
| func->name = Name(std::string("legalfunc$") + im->name.str); |
| funcName = func->name; |
| |
| auto* call = module->allocator.alloc<CallImport>(); |
| call->target = legal->name; |
| |
| for (auto param : im->functionType->params) { |
| if (param == i64) { |
| call->operands.push_back(I64Utilities::getI64Low(builder, func->params.size())); |
| call->operands.push_back(I64Utilities::getI64High(builder, func->params.size())); |
| type->params.push_back(i32); |
| type->params.push_back(i32); |
| } else if (param == f32) { |
| call->operands.push_back(builder.makeUnary(PromoteFloat32, builder.makeGetLocal(func->params.size(), f32))); |
| type->params.push_back(f64); |
| } else { |
| call->operands.push_back(builder.makeGetLocal(func->params.size(), param)); |
| type->params.push_back(param); |
| } |
| func->params.push_back(param); |
| } |
| |
| if (im->functionType->result == i64) { |
| call->type = i32; |
| Expression* get; |
| ensureTempRet0(module); |
| get = builder.makeGetGlobal(TEMP_RET_0, i32); |
| func->body = I64Utilities::recreateI64(builder, call, get); |
| type->result = i32; |
| } else if (im->functionType->result == f32) { |
| call->type = f64; |
| func->body = builder.makeUnary(DemoteFloat64, call); |
| type->result = f64; |
| } else { |
| call->type = im->functionType->result; |
| func->body = call; |
| type->result = im->functionType->result; |
| } |
| func->result = im->functionType->result; |
| |
| module->addFunction(func); |
| module->addFunctionType(type); |
| return legal; |
| } |
| |
| void ensureTempRet0(Module* module) { |
| if (!module->checkGlobal(TEMP_RET_0)) { |
| Global* global = new Global; |
| global->name = TEMP_RET_0; |
| global->type = i32; |
| global->init = module->allocator.alloc<Const>()->set(Literal(int32_t(0))); |
| global->mutable_ = true; |
| module->addGlobal(global); |
| } |
| } |
| }; |
| |
| Pass *createLegalizeJSInterfacePass() { |
| return new LegalizeJSInterface(); |
| } |
| |
| } // namespace wasm |
| |