diff options
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r-- | src/asm2wasm.h | 320 |
1 files changed, 160 insertions, 160 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 3ea9a3a1e..44666c1e5 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -33,7 +33,9 @@ #include "parsing.h" #include "ir/bits.h" #include "ir/branch-utils.h" +#include "ir/function-type-utils.h" #include "ir/literal-utils.h" +#include "ir/module-utils.h" #include "ir/trapping.h" #include "ir/utils.h" #include "wasm-builder.h" @@ -343,8 +345,8 @@ struct Asm2WasmPreProcessor { } }; -static CallImport* checkDebugInfo(Expression* curr) { - if (auto* call = curr->dynCast<CallImport>()) { +static Call* checkDebugInfo(Expression* curr) { + if (auto* call = curr->dynCast<Call>()) { if (call->target == EMSCRIPTEN_DEBUGINFO) { return call; } @@ -478,7 +480,7 @@ private: std::map<IString, std::unique_ptr<FunctionType>> importedFunctionTypes; - void noteImportedFunctionCall(Ref ast, Type resultType, CallImport* call) { + void noteImportedFunctionCall(Ref ast, Type resultType, Call* call) { assert(ast[0] == CALL && ast[1]->isString()); IString importName = ast[1]->getIString(); auto type = make_unique<FunctionType>(); @@ -696,7 +698,6 @@ private: void fixCallType(Expression* call, Type type) { if (call->is<Call>()) call->cast<Call>()->type = type; - if (call->is<CallImport>()) call->cast<CallImport>()->type = type; else if (call->is<CallIndirect>()) call->cast<CallIndirect>()->type = type; } @@ -762,45 +763,35 @@ void Asm2WasmBuilder::processAsm(Ref ast) { } // import memory - auto memoryImport = make_unique<Import>(); - memoryImport->name = MEMORY; - memoryImport->module = ENV; - memoryImport->base = MEMORY; - memoryImport->kind = ExternalKind::Memory; + wasm.memory.name = MEMORY; + wasm.memory.module = ENV; + wasm.memory.base = MEMORY; wasm.memory.exists = true; - wasm.memory.imported = true; - wasm.addImport(memoryImport.release()); // import table - auto tableImport = make_unique<Import>(); - tableImport->name = TABLE; - tableImport->module = ENV; - tableImport->base = TABLE; - tableImport->kind = ExternalKind::Table; - wasm.addImport(tableImport.release()); + wasm.table.name = TABLE; + wasm.table.module = ENV; + wasm.table.base = TABLE; wasm.table.exists = true; - wasm.table.imported = true; // Import memory offset, if not already there { - auto* import = new Import; - import->name = Name("memoryBase"); - import->module = Name("env"); - import->base = Name("memoryBase"); - import->kind = ExternalKind::Global; - import->globalType = i32; - wasm.addImport(import); + auto* import = new Global; + import->name = "memoryBase"; + import->module = "env"; + import->base = "memoryBase"; + import->type = i32; + wasm.addGlobal(import); } // Import table offset, if not already there { - auto* import = new Import; - import->name = Name("tableBase"); - import->module = Name("env"); - import->base = Name("tableBase"); - import->kind = ExternalKind::Global; - import->globalType = i32; - wasm.addImport(import); + auto* import = new Global; + import->name = "tableBase"; + import->module = "env"; + import->base = "tableBase"; + import->type = i32; + wasm.addGlobal(import); } auto addImport = [&](IString name, Ref imported, Type type) { @@ -907,18 +898,18 @@ void Asm2WasmBuilder::processAsm(Ref ast) { } } } - auto import = make_unique<Import>(); - import->name = name; - import->module = moduleName; - import->base = imported[2]->getIString(); + auto base = imported[2]->getIString(); // special-case some asm builtins - if (import->module == GLOBAL && (import->base == NAN_ || import->base == INFINITY_)) { + if (module == GLOBAL && (base == NAN_ || base == INFINITY_)) { type = Type::f64; } if (type != Type::none) { // this is a global - import->kind = ExternalKind::Global; - import->globalType = type; + auto* import = new Global; + import->name = name; + import->module = moduleName; + import->base = base; + import->type = type; mappedGlobals.emplace(name, type); // tableBase and memoryBase are used as segment/element offsets, and must be constant; // otherwise, an asm.js import of a constant is mutable, e.g. STACKTOP @@ -935,15 +926,19 @@ void Asm2WasmBuilder::processAsm(Ref ast) { )); } } + if ((name == "tableBase" || name == "memoryBase") && + wasm.getGlobalOrNull(import->base)) { + return; + } + wasm.addGlobal(import); } else { - import->kind = ExternalKind::Function; + // this is a function + auto* import = new Function; + import->name = name; + import->module = moduleName; + import->base = base; + wasm.addFunction(import); } - // we may have already created an import for this manually - if ((name == "tableBase" || name == "memoryBase") && - (wasm.getImportOrNull(import->base) || wasm.getGlobalOrNull(import->base))) { - return; - } - wasm.addImport(import.release()); }; IString Int8Array, Int16Array, Int32Array, UInt8Array, UInt16Array, UInt32Array, Float32Array, Float64Array; @@ -1205,27 +1200,32 @@ void Asm2WasmBuilder::processAsm(Ref ast) { std::vector<IString> toErase; - for (auto& import : wasm.imports) { - if (import->kind != ExternalKind::Function) continue; + ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { IString name = import->name; if (importedFunctionTypes.find(name) != importedFunctionTypes.end()) { // special math builtins FunctionType* builtin = getBuiltinFunctionType(import->module, import->base); if (builtin) { - import->functionType = builtin->name; - continue; + import->type = builtin->name; + } else { + import->type = ensureFunctionType(getSig(importedFunctionTypes[name].get()), &wasm)->name; } - import->functionType = ensureFunctionType(getSig(importedFunctionTypes[name].get()), &wasm)->name; } else if (import->module != ASM2WASM) { // special-case the special module // never actually used, which means we don't know the function type since the usage tells us, so illegal for it to remain toErase.push_back(name); } - } + }); for (auto curr : toErase) { - wasm.removeImport(curr); + wasm.removeFunction(curr); } + // Finalize function imports now that we've seen all the calls + + ModuleUtils::iterImportedFunctions(wasm, [&](Function* func) { + FunctionTypeUtils::fillFunction(func, wasm.getFunctionType(func->type)); + }); + // Finalize calls now that everything is known and generated struct FinalizeCalls : public WalkerPass<PostWalker<FinalizeCalls>> { @@ -1258,96 +1258,94 @@ void Asm2WasmBuilder::processAsm(Ref ast) { } void visitCall(Call* curr) { + // The call target may not exist if it is one of our special fake imports for callIndirect fixups auto* calledFunc = getModule()->getFunctionOrNull(curr->target); - if (!calledFunc) { - std::cerr << "invalid call target: " << curr->target << '\n'; - WASM_UNREACHABLE(); - } - // The result type of the function being called is now known, and can be applied. - auto result = calledFunc->result; - if (curr->type != result) { - curr->type = result; - } - // Handle mismatched numbers of arguments. In clang, if a function is declared one way - // but called in another, it inserts bitcasts to make things work. Those end up - // working since it is "ok" to drop or add parameters in native platforms, even - // though it's undefined behavior. We warn about it here, but tolerate it, if there is - // a simple solution. - if (curr->operands.size() < calledFunc->params.size()) { - notifyAboutWrongOperands("warning: asm2wasm adding operands", calledFunc); - while (curr->operands.size() < calledFunc->params.size()) { - // Add params as necessary, with zeros. - curr->operands.push_back( - LiteralUtils::makeZero(calledFunc->params[curr->operands.size()], *getModule()) - ); + if (calledFunc && !calledFunc->imported()) { + // The result type of the function being called is now known, and can be applied. + auto result = calledFunc->result; + if (curr->type != result) { + curr->type = result; } - } - if (curr->operands.size() > calledFunc->params.size()) { - notifyAboutWrongOperands("warning: asm2wasm dropping operands", calledFunc); - curr->operands.resize(calledFunc->params.size()); - } - // If the types are wrong, validation will fail later anyhow, but add a warning here, - // it may help people. - for (Index i = 0; i < curr->operands.size(); i++) { - auto sent = curr->operands[i]->type; - auto expected = calledFunc->params[i]; - if (sent != unreachable && sent != expected) { - notifyAboutWrongOperands("error: asm2wasm seeing an invalid argument type at index " + std::to_string(i) + " (this will not validate)", calledFunc); + // Handle mismatched numbers of arguments. In clang, if a function is declared one way + // but called in another, it inserts bitcasts to make things work. Those end up + // working since it is "ok" to drop or add parameters in native platforms, even + // though it's undefined behavior. We warn about it here, but tolerate it, if there is + // a simple solution. + if (curr->operands.size() < calledFunc->params.size()) { + notifyAboutWrongOperands("warning: asm2wasm adding operands", calledFunc); + while (curr->operands.size() < calledFunc->params.size()) { + // Add params as necessary, with zeros. + curr->operands.push_back( + LiteralUtils::makeZero(calledFunc->params[curr->operands.size()], *getModule()) + ); + } } - } - } - - void visitCallImport(CallImport* curr) { - // fill out call_import - add extra params as needed, etc. asm tolerates ffi overloading, wasm does not - auto iter = parent->importedFunctionTypes.find(curr->target); - if (iter == parent->importedFunctionTypes.end()) return; // one of our fake imports for callIndirect fixups - auto type = iter->second.get(); - for (size_t i = 0; i < type->params.size(); i++) { - if (i >= curr->operands.size()) { - // add a new param - auto val = parent->allocator.alloc<Const>(); - val->type = val->value.type = type->params[i]; - curr->operands.push_back(val); - } else if (curr->operands[i]->type != type->params[i]) { - // if the param is used, then we have overloading here and the combined type must be f64; - // if this is an unreachable param, then it doesn't matter. - assert(type->params[i] == f64 || curr->operands[i]->type == unreachable); - // overloaded, upgrade to f64 - switch (curr->operands[i]->type) { - case i32: curr->operands[i] = parent->builder.makeUnary(ConvertSInt32ToFloat64, curr->operands[i]); break; - case f32: curr->operands[i] = parent->builder.makeUnary(PromoteFloat32, curr->operands[i]); break; - default: {} // f64, unreachable, etc., are all good + if (curr->operands.size() > calledFunc->params.size()) { + notifyAboutWrongOperands("warning: asm2wasm dropping operands", calledFunc); + curr->operands.resize(calledFunc->params.size()); + } + // If the types are wrong, validation will fail later anyhow, but add a warning here, + // it may help people. + for (Index i = 0; i < curr->operands.size(); i++) { + auto sent = curr->operands[i]->type; + auto expected = calledFunc->params[i]; + if (sent != unreachable && sent != expected) { + notifyAboutWrongOperands("error: asm2wasm seeing an invalid argument type at index " + std::to_string(i) + " (this will not validate)", calledFunc); } } - } - Module* wasm = getModule(); - auto importResult = wasm->getFunctionType(wasm->getImport(curr->target)->functionType)->result; - if (curr->type != importResult) { - auto old = curr->type; - curr->type = importResult; - if (importResult == f64) { - // we use a JS f64 value which is the most general, and convert to it - switch (old) { - case i32: { - Unary* trunc = parent->builder.makeUnary(TruncSFloat64ToInt32, curr); - replaceCurrent(makeTrappingUnary(trunc, parent->trappingFunctions)); - break; - } - case f32: { - replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr)); - break; + } else { + // A call to an import + // fill things out: add extra params as needed, etc. asm tolerates ffi overloading, wasm does not + auto iter = parent->importedFunctionTypes.find(curr->target); + if (iter == parent->importedFunctionTypes.end()) return; // one of our fake imports for callIndirect fixups + auto type = iter->second.get(); + for (size_t i = 0; i < type->params.size(); i++) { + if (i >= curr->operands.size()) { + // add a new param + auto val = parent->allocator.alloc<Const>(); + val->type = val->value.type = type->params[i]; + curr->operands.push_back(val); + } else if (curr->operands[i]->type != type->params[i]) { + // if the param is used, then we have overloading here and the combined type must be f64; + // if this is an unreachable param, then it doesn't matter. + assert(type->params[i] == f64 || curr->operands[i]->type == unreachable); + // overloaded, upgrade to f64 + switch (curr->operands[i]->type) { + case i32: curr->operands[i] = parent->builder.makeUnary(ConvertSInt32ToFloat64, curr->operands[i]); break; + case f32: curr->operands[i] = parent->builder.makeUnary(PromoteFloat32, curr->operands[i]); break; + default: {} // f64, unreachable, etc., are all good } - case none: { - // this function returns a value, but we are not using it, so it must be dropped. - // autodrop will do that for us. - break; + } + } + Module* wasm = getModule(); + auto importResult = wasm->getFunctionType(wasm->getFunction(curr->target)->type)->result; + if (curr->type != importResult) { + auto old = curr->type; + curr->type = importResult; + if (importResult == f64) { + // we use a JS f64 value which is the most general, and convert to it + switch (old) { + case i32: { + Unary* trunc = parent->builder.makeUnary(TruncSFloat64ToInt32, curr); + replaceCurrent(makeTrappingUnary(trunc, parent->trappingFunctions)); + break; + } + case f32: { + replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr)); + break; + } + case none: { + // this function returns a value, but we are not using it, so it must be dropped. + // autodrop will do that for us. + break; + } + default: WASM_UNREACHABLE(); } - default: WASM_UNREACHABLE(); + } else { + assert(old == none); + // we don't want a return value here, but the import does provide one + // autodrop will do that for us. } - } else { - assert(old == none); - // we don't want a return value here, but the import does provide one - // autodrop will do that for us. } } } @@ -1361,7 +1359,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { target = block->list.back(); } // the something might have been optimized out, leaving only the call - if (auto* call = target->dynCast<CallImport>()) { + if (auto* call = target->dynCast<Call>()) { auto tableName = call->target; if (parent->functionTableStarts.find(tableName) == parent->functionTableStarts.end()) return; curr->target = parent->builder.makeConst(Literal((int32_t)parent->functionTableStarts[tableName])); @@ -1369,13 +1367,13 @@ void Asm2WasmBuilder::processAsm(Ref ast) { } auto* add = target->dynCast<Binary>(); if (!add) return; - if (add->right->is<CallImport>()) { - auto* offset = add->right->cast<CallImport>(); + if (add->right->is<Call>()) { + auto* offset = add->right->cast<Call>(); auto tableName = offset->target; if (parent->functionTableStarts.find(tableName) == parent->functionTableStarts.end()) return; add->right = parent->builder.makeConst(Literal((int32_t)parent->functionTableStarts[tableName])); } else { - auto* offset = add->left->dynCast<CallImport>(); + auto* offset = add->left->dynCast<Call>(); if (!offset) return; auto tableName = offset->target; if (parent->functionTableStarts.find(tableName) == parent->functionTableStarts.end()) return; @@ -1400,7 +1398,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { name = "apply-debug-info"; } - CallImport* lastDebugInfo = nullptr; + Call* lastDebugInfo = nullptr; void visitExpression(Expression* curr) { if (auto* call = checkDebugInfo(curr)) { @@ -1483,7 +1481,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { // remove the debug info intrinsic if (preprocessor.debugInfo) { - wasm.removeImport(EMSCRIPTEN_DEBUGINFO); + wasm.removeFunction(EMSCRIPTEN_DEBUGINFO); } if (udivmoddi4.is() && getTempRet0.is()) { @@ -1630,19 +1628,20 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { return ret; } if (name == DEBUGGER) { - CallImport *call = allocator.alloc<CallImport>(); + Call *call = allocator.alloc<Call>(); call->target = DEBUGGER; call->type = none; static bool addedImport = false; if (!addedImport) { addedImport = true; - auto import = new Import; // debugger = asm2wasm.debugger; + auto import = new Function; // debugger = asm2wasm.debugger; import->name = DEBUGGER; import->module = ASM2WASM; import->base = DEBUGGER; - import->functionType = ensureFunctionType("v", &wasm)->name; - import->kind = ExternalKind::Function; - wasm.addImport(import); + auto* functionType = ensureFunctionType("v", &wasm); + import->type = functionType->name; + FunctionTypeUtils::fillFunction(import, functionType); + wasm.addFunction(import); } return call; } @@ -1732,7 +1731,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->finalize(); if (ret->op == BinaryOp::RemSInt32 && isFloatType(ret->type)) { // WebAssembly does not have floating-point remainder, we have to emit a call to a special import of ours - CallImport *call = allocator.alloc<CallImport>(); + Call *call = allocator.alloc<Call>(); call->target = F64_REM; call->operands.push_back(ensureDouble(ret->left)); call->operands.push_back(ensureDouble(ret->right)); @@ -1740,13 +1739,14 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { static bool addedImport = false; if (!addedImport) { addedImport = true; - auto import = new Import; // f64-rem = asm2wasm.f64-rem; + auto import = new Function; // f64-rem = asm2wasm.f64-rem; import->name = F64_REM; import->module = ASM2WASM; import->base = F64_REM; - import->functionType = ensureFunctionType("ddd", &wasm)->name; - import->kind = ExternalKind::Function; - wasm.addImport(import); + auto* functionType = ensureFunctionType("ddd", &wasm); + import->type = functionType->name; + FunctionTypeUtils::fillFunction(import, functionType); + wasm.addFunction(import); } return call; } @@ -2200,7 +2200,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } Expression* ret; ExpressionList* operands; - CallImport* callImport = nullptr; + bool callImport = false; Index firstOperand = 0; Ref args = ast[2]; if (tableCall) { @@ -2209,12 +2209,11 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { firstOperand = 1; operands = &specific->operands; ret = specific; - } else if (wasm.getImportOrNull(name)) { - callImport = allocator.alloc<CallImport>(); - callImport->target = name; - operands = &callImport->operands; - ret = callImport; } else { + // if we call an import, it definitely exists already; if it's a + // defined function then it might not have been seen yet + auto* target = wasm.getFunctionOrNull(name); + callImport = target && target->imported(); auto specific = allocator.alloc<Call>(); specific->target = name; operands = &specific->operands; @@ -2239,8 +2238,9 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { // this is important as we run the optimizer on functions before we get // to finalizeCalls (which we can only do once we've read all the functions, // and we optimize in parallel starting earlier). - callImport->type = getResultTypeOfCallUsingParent(astStackHelper.getParent(), &asmData); - noteImportedFunctionCall(ast, callImport->type, callImport); + auto* call = ret->cast<Call>(); + call->type = getResultTypeOfCallUsingParent(astStackHelper.getParent(), &asmData); + noteImportedFunctionCall(ast, call->type, call); } return ret; } @@ -2257,7 +2257,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->fullType = fullType->name; ret->type = fullType->result; // we don't know the table offset yet. emit target = target + callImport(tableName), which we fix up later when we know how asm function tables are layed out inside the wasm table. - ret->target = builder.makeBinary(BinaryOp::AddInt32, ret->target, builder.makeCallImport(target[1]->getIString(), {}, i32)); + ret->target = builder.makeBinary(BinaryOp::AddInt32, ret->target, builder.makeCall(target[1]->getIString(), {}, i32)); return ret; } else if (what == RETURN) { Type type = !!ast[1] ? detectWasmType(ast[1], &asmData) : none; |