diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/abi/js.h | 52 | ||||
-rw-r--r-- | src/asmjs/shared-constants.cpp | 16 | ||||
-rw-r--r-- | src/passes/I64ToI32Lowering.cpp | 59 | ||||
-rw-r--r-- | src/passes/RemoveNonJSOps.cpp | 4 | ||||
-rw-r--r-- | src/passes/wasm-intrinsics.wast | 29 | ||||
-rw-r--r-- | src/wasm2js.h | 159 |
6 files changed, 237 insertions, 82 deletions
diff --git a/src/abi/js.h b/src/abi/js.h index bcc7dbb6e..6994a4291 100644 --- a/src/abi/js.h +++ b/src/abi/js.h @@ -18,6 +18,7 @@ #define wasm_abi_abi_h #include "wasm.h" +#include "asmjs/shared-constants.h" namespace wasm { @@ -36,6 +37,57 @@ inline std::string getLegalizationPass(LegalizationLevel level) { } } +namespace wasm2js { + +extern cashew::IString SCRATCH_LOAD_I32, + SCRATCH_STORE_I32, + SCRATCH_LOAD_I64, + SCRATCH_STORE_I64, + SCRATCH_LOAD_F32, + SCRATCH_STORE_F32, + SCRATCH_LOAD_F64, + SCRATCH_STORE_F64; + +// The wasm2js scratch memory helpers let us read and write to scratch memory +// for purposes of implementing things like reinterpret, etc. +// The optional "specific" parameter is a specific function we want. If not +// provided, we create them all. +inline void ensureScratchMemoryHelpers(Module* wasm, cashew::IString specific = cashew::IString()) { + auto ensureImport = [&](Name name, const std::vector<Type> params, Type result) { + if (wasm->getFunctionOrNull(name)) return; + if (specific.is() && name != specific) return; + auto func = make_unique<Function>(); + func->name = name; + func->params = params; + func->result = result; + func->module = ENV; + func->base = name; + wasm->addFunction(std::move(func)); + }; + + ensureImport(SCRATCH_LOAD_I32, { i32 }, i32); + ensureImport(SCRATCH_STORE_I32, { i32, i32 }, none); + ensureImport(SCRATCH_LOAD_I64, {}, i64); + ensureImport(SCRATCH_STORE_I64, { i64 }, none); + ensureImport(SCRATCH_LOAD_F32, {}, f32); + ensureImport(SCRATCH_STORE_F32, { f32 }, none); + ensureImport(SCRATCH_LOAD_F64, {}, f64); + ensureImport(SCRATCH_STORE_F64, { f64 }, none); +} + +inline bool isScratchMemoryHelper(cashew::IString name) { + return name == SCRATCH_LOAD_I32 || + name == SCRATCH_STORE_I32 || + name == SCRATCH_LOAD_I64 || + name == SCRATCH_STORE_I64 || + name == SCRATCH_LOAD_F32 || + name == SCRATCH_STORE_F32 || + name == SCRATCH_LOAD_F64 || + name == SCRATCH_STORE_F64; +} + +} // namespace wasm2js + } // namespace ABI } // namespace wasm diff --git a/src/asmjs/shared-constants.cpp b/src/asmjs/shared-constants.cpp index 0cf3668b1..ec04b63e7 100644 --- a/src/asmjs/shared-constants.cpp +++ b/src/asmjs/shared-constants.cpp @@ -102,4 +102,20 @@ cashew::IString GLOBAL("global"), WASM_I64_UDIV("__wasm_i64_udiv"), WASM_I64_SREM("__wasm_i64_srem"), WASM_I64_UREM("__wasm_i64_urem"); + +namespace ABI { +namespace wasm2js { + +cashew::IString SCRATCH_LOAD_I32("wasm2js_scratch_load_i32"), + SCRATCH_STORE_I32("wasm2js_scratch_store_i32"), + SCRATCH_LOAD_I64("wasm2js_scratch_load_i64"), + SCRATCH_STORE_I64("wasm2js_scratch_store_i64"), + SCRATCH_LOAD_F32("wasm2js_scratch_load_f32"), + SCRATCH_STORE_F32("wasm2js_scratch_store_f32"), + SCRATCH_LOAD_F64("wasm2js_scratch_load_f64"), + SCRATCH_STORE_F64("wasm2js_scratch_store_f64"); + +} // namespace wasm2js +} // namespace ABI + } diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 33bb7229e..e6dc90607 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -27,6 +27,7 @@ #include "emscripten-optimizer/istring.h" #include "support/name.h" #include "wasm-builder.h" +#include "abi/js.h" #include "ir/flat.h" #include "ir/iteration.h" #include "ir/memory-utils.h" @@ -337,25 +338,30 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { template<typename T> using BuilderFunc = std::function<T*(std::vector<Expression*>&, Type)>; + // Fixes up a call. If we performed fixups, returns the call; otherwise returns nullptr; template<typename T> - void visitGenericCall(T* curr, BuilderFunc<T> callBuilder) { + T* visitGenericCall(T* curr, BuilderFunc<T> callBuilder) { + bool fixed = false; std::vector<Expression*> args; for (auto* e : curr->operands) { args.push_back(e); if (hasOutParam(e)) { TempVar argHighBits = fetchOutParam(e); args.push_back(builder->makeGetLocal(argHighBits, i32)); + fixed = true; } } if (curr->type != i64) { - replaceCurrent(callBuilder(args, curr->type)); - return; + auto* ret = callBuilder(args, curr->type); + replaceCurrent(ret); + return fixed ? ret : nullptr; } TempVar lowBits = getTemp(); TempVar highBits = getTemp(); + auto* call = callBuilder(args, i32); SetLocal* doCall = builder->makeSetLocal( lowBits, - callBuilder(args, i32) + call ); SetLocal* setHigh = builder->makeSetLocal( highBits, @@ -365,14 +371,21 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { Block* result = builder->blockify(doCall, setHigh, getLow); setOutParam(result, std::move(highBits)); replaceCurrent(result); + return call; } void visitCall(Call* curr) { - visitGenericCall<Call>( + auto* fixedCall = visitGenericCall<Call>( curr, [&](std::vector<Expression*>& args, Type ty) { return builder->makeCall(curr->target, args, ty); } ); + // If this was to an import, we need to call the legal version. This assumes + // that legalize-js-interface has been run before. + if (fixedCall && getModule()->getFunction(fixedCall->target)->imported()) { + fixedCall->target = std::string("legalfunc$") + fixedCall->target.str; + return; + } } void visitCallIndirect(CallIndirect* curr) { @@ -635,17 +648,17 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { // our f64 through memory at address 0 TempVar highBits = getTemp(); Block *result = builder->blockify( - builder->makeStore(8, 0, 8, makeGetTempMemory(), curr->value, f64), + builder->makeCall(ABI::wasm2js::SCRATCH_STORE_F64, { curr->value }, none), builder->makeSetLocal( highBits, - builder->makeLoad(4, true, 4, 4, makeGetTempMemory(), i32) + builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, { builder->makeConst(Literal(int32_t(1))) }, i32) ), - builder->makeLoad(4, true, 0, 4, makeGetTempMemory(), i32) + builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, { builder->makeConst(Literal(int32_t(0))) }, i32) ); setOutParam(result, std::move(highBits)); replaceCurrent(result); MemoryUtils::ensureExists(getModule()->memory); - ensureTempMemoryGlobal(); + ABI::wasm2js::ensureScratchMemoryHelpers(getModule()); } void lowerReinterpretInt64(Unary* curr) { @@ -653,33 +666,13 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { // our i64 through memory at address 0 TempVar highBits = fetchOutParam(curr->value); Block *result = builder->blockify( - builder->makeStore(4, 0, 4, makeGetTempMemory(), curr->value, i32), - builder->makeStore(4, 4, 4, makeGetTempMemory(), builder->makeGetLocal(highBits, i32), i32), - builder->makeLoad(8, true, 0, 8, makeGetTempMemory(), f64) + builder->makeCall(ABI::wasm2js::SCRATCH_STORE_I32, { builder->makeConst(Literal(int32_t(0))), curr->value }, none), + builder->makeCall(ABI::wasm2js::SCRATCH_STORE_I32, { builder->makeConst(Literal(int32_t(1))), builder->makeGetLocal(highBits, i32) }, none), + builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_F64, {}, f64) ); replaceCurrent(result); MemoryUtils::ensureExists(getModule()->memory); - ensureTempMemoryGlobal(); - } - - Name tempMemory = "__tempMemory__"; - - void ensureTempMemoryGlobal() { - // Ensure the existence of an imported global, __tempMemory__, which points to 8 - // bytes of scratch memory we can use for roundtrip purposes. - if (!getModule()->getGlobalOrNull(tempMemory)) { - auto global = make_unique<Global>(); - global->name = tempMemory; - global->type = i32; - global->mutable_ = false; - global->module = ENV; - global->base = tempMemory; - getModule()->addGlobal(global.release()); - } - } - - Expression* makeGetTempMemory() { - return builder->makeGetGlobal(tempMemory, i32); + ABI::wasm2js::ensureScratchMemoryHelpers(getModule()); } void lowerTruncFloatToInt(Unary *curr) { diff --git a/src/passes/RemoveNonJSOps.cpp b/src/passes/RemoveNonJSOps.cpp index 906c34bc4..fc3e42185 100644 --- a/src/passes/RemoveNonJSOps.cpp +++ b/src/passes/RemoveNonJSOps.cpp @@ -33,6 +33,7 @@ #include "asmjs/shared-constants.h" #include "wasm-builder.h" #include "wasm-s-parser.h" +#include "abi/js.h" #include "ir/memory-utils.h" #include "ir/module-utils.h" #include "ir/find_all.h" @@ -50,6 +51,9 @@ struct RemoveNonJSOpsPass : public WalkerPass<PostWalker<RemoveNonJSOpsPass>> { Pass* create() override { return new RemoveNonJSOpsPass; } void doWalkModule(Module* module) { + // Intrinsics may use scratch memory, ensure it. + ABI::wasm2js::ensureScratchMemoryHelpers(module); + // Discover all of the intrinsics that we need to inject, lowering all // operations to intrinsic calls while we're at it. if (!builder) builder = make_unique<Builder>(*module); diff --git a/src/passes/wasm-intrinsics.wast b/src/passes/wasm-intrinsics.wast index a8ee5be0b..f54547a0d 100644 --- a/src/passes/wasm-intrinsics.wast +++ b/src/passes/wasm-intrinsics.wast @@ -7,8 +7,8 @@ ;; ;; LOCAL MODS done by hand afterwards: ;; * Remove hardcoded address 1024 (apparently a free memory location rustc -;; thinks is ok to use?); add a global __tempMemory__ which is used for that -;; purpose. +;; thinks is ok to use?); add intrinsic functions, which load/store to +;; special scratch space, wasm2js_scratch_load_i32 etc. ;; * Fix function type of __wasm_ctz_i64, which was wrong somehow, ;; i32, i32 => i32 instead of i64 => i64 ;; @@ -22,7 +22,8 @@ (type $4 (func (param i32 i32) (result i32))) (type $5 (func (param i64) (result i64))) (import "env" "memory" (memory $0 17)) - (import "env" "__tempMemory__" (global $__tempMemory__ i32)) + (import "env" "wasm2js_scratch_load_i64" (func $wasm2js_scratch_load_i64 (result i64))) + (import "env" "wasm2js_scratch_store_i64" (func $wasm2js_scratch_store_i64 (param i64))) (export "__wasm_i64_sdiv" (func $__wasm_i64_sdiv)) (export "__wasm_i64_udiv" (func $__wasm_i64_udiv)) (export "__wasm_i64_srem" (func $__wasm_i64_srem)) @@ -136,9 +137,7 @@ (local.get $var$1) ) ) - (i64.load - (global.get $__tempMemory__) - ) + (call $wasm2js_scratch_load_i64) ) ;; lowering of the i64.mul instruction, return $var0 * $var$1 (func $__wasm_i64_mul (; 4 ;) (type $0) (param $var$0 i64) (param $var$1 i64) (result i64) @@ -579,8 +578,7 @@ (i64.const 4294967296) ) ) - (i64.store - (global.get $__tempMemory__) + (call $wasm2js_scratch_store_i64 (i64.extend_i32_u (i32.sub (local.tee $var$2 @@ -641,8 +639,7 @@ (local.get $var$3) ) ) - (i64.store - (global.get $__tempMemory__) + (call $wasm2js_scratch_store_i64 (i64.or (i64.shl (i64.extend_i32_u @@ -722,8 +719,7 @@ ) (br $label$3) ) - (i64.store - (global.get $__tempMemory__) + (call $wasm2js_scratch_store_i64 (i64.shl (i64.extend_i32_u (i32.sub @@ -765,8 +761,7 @@ ) (br $label$2) ) - (i64.store - (global.get $__tempMemory__) + (call $wasm2js_scratch_store_i64 (i64.extend_i32_u (i32.and (local.get $var$4) @@ -897,8 +892,7 @@ ) ) ) - (i64.store - (global.get $__tempMemory__) + (call $wasm2js_scratch_store_i64 (local.get $var$5) ) (return @@ -911,8 +905,7 @@ ) ) ) - (i64.store - (global.get $__tempMemory__) + (call $wasm2js_scratch_store_i64 (local.get $var$0) ) (local.set $var$0 diff --git a/src/wasm2js.h b/src/wasm2js.h index faad6c3fa..311f8be10 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -34,6 +34,7 @@ #include "emscripten-optimizer/optimizer.h" #include "mixed_arena.h" #include "asm_v_wasm.h" +#include "abi/js.h" #include "ir/import-utils.h" #include "ir/load-utils.h" #include "ir/module-utils.h" @@ -288,6 +289,10 @@ private: }; Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { + // Ensure the scratch memory helpers. + // If later on they aren't needed, we'll clean them up. + ABI::wasm2js::ensureScratchMemoryHelpers(wasm); + PassRunner runner(wasm); runner.add<AutoDrop>(); runner.add("legalize-js-interface"); @@ -485,6 +490,10 @@ void Wasm2JSBuilder::addBasics(Ref ast) { } void Wasm2JSBuilder::addFunctionImport(Ref ast, Function* import) { + // The scratch memory helpers are emitted in the glue, see code and comments below. + if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { + return; + } Ref theVar = ValueBuilder::makeVar(); ast->push_back(theVar); Ref module = ValueBuilder::makeName(ENV); // TODO: handle nested module imports @@ -1194,12 +1203,7 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, Function* func, IString resul return ValueBuilder::makeSeq(ptrSet, rest); } // normal load - Ref ptr = visit(curr->ptr, EXPRESSION_RESULT); - if (curr->offset) { - ptr = makeAsmCoercion( - ValueBuilder::makeBinary(ptr, PLUS, ValueBuilder::makeNum(curr->offset)), - ASM_INT); - } + Ref ptr = makePointer(curr->ptr, curr->offset); Ref ret; switch (curr->type) { case i32: { @@ -1322,10 +1326,7 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, Function* func, IString resul return ValueBuilder::makeSeq(ValueBuilder::makeSeq(ptrSet, valueSet), rest); } // normal store - Ref ptr = visit(curr->ptr, EXPRESSION_RESULT); - if (curr->offset) { - ptr = makeAsmCoercion(ValueBuilder::makeBinary(ptr, PLUS, ValueBuilder::makeNum(curr->offset)), ASM_INT); - } + Ref ptr = makePointer(curr->ptr, curr->offset); Ref value = visit(curr->value, EXPRESSION_RESULT); Ref ret; switch (curr->valueType) { @@ -1419,20 +1420,15 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, Function* func, IString resul EXPRESSION_RESULT), ASM_INT), EQ, makeAsmCoercion(ValueBuilder::makeInt(0), ASM_INT)); case ReinterpretFloat32: { - // Naively assume that the address 0 and the next 4 bytes are - // permanently unused by the source program, which is definitely - // true for languages like C/C++/Rust - Ref zero = ValueBuilder::makeInt(0); - Ref ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), zero); - Ref value = visit(curr->value, EXPRESSION_RESULT); - Ref store = ValueBuilder::makeBinary(ret, SET, value); - return ValueBuilder::makeSeq( - store, - makeAsmCoercion( - ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), zero), - ASM_INT - ) + ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_STORE_F32); + ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_LOAD_I32); + + Ref store = ValueBuilder::makeCall( + ABI::wasm2js::SCRATCH_STORE_F32, + visit(curr->value, EXPRESSION_RESULT) ); + Ref load = ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, ValueBuilder::makeInt(0)); + return ValueBuilder::makeSeq(store, load); } // generate (~~expr), what Emscripten does case TruncSFloat32ToInt32: @@ -1512,15 +1508,16 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, Function* func, IString resul return makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT), ASM_FLOAT); case ReinterpretInt32: { - // Like above, assume address 0 is unused. - Ref zero = ValueBuilder::makeInt(0); - Ref ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), zero); - Ref value = visit(curr->value, EXPRESSION_RESULT); - Ref store = ValueBuilder::makeBinary(ret, SET, value); - return ValueBuilder::makeSeq( - store, - ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), zero) + ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_STORE_I32); + ABI::wasm2js::ensureScratchMemoryHelpers(module, ABI::wasm2js::SCRATCH_LOAD_F32); + + Ref store = ValueBuilder::makeCall( + ABI::wasm2js::SCRATCH_STORE_I32, + ValueBuilder::makeNum(0), + visit(curr->value, EXPRESSION_RESULT) ); + Ref load = ValueBuilder::makeCall(ABI::wasm2js::SCRATCH_LOAD_F32); + return ValueBuilder::makeSeq(store, load); } // Coerce the integer to a float as emscripten does case ConvertSInt32ToFloat32: @@ -1858,7 +1855,19 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, Function* func, IString resul Ref visitUnreachable(Unreachable* curr) { return ValueBuilder::makeCall(ABORT_FUNC); } + + private: + Ref makePointer(Expression* ptr, Address offset) { + auto ret = visit(ptr, EXPRESSION_RESULT); + if (offset) { + ret = makeAsmCoercion( + ValueBuilder::makeBinary(ret, PLUS, ValueBuilder::makeNum(offset)), + ASM_INT); + } + return ret; + } }; + return ExpressionProcessor(this, m, func).visit(func->body, result); } @@ -2049,6 +2058,7 @@ private: void emitPostES6(); void emitMemory(std::string buffer, std::string segmentWriter); + void emitScratchMemorySupport(); }; void Wasm2JSGlue::emitPre() { @@ -2057,6 +2067,8 @@ void Wasm2JSGlue::emitPre() { } else { emitPreES6(); } + + emitScratchMemorySupport(); } void Wasm2JSGlue::emitPreEmscripten() { @@ -2089,6 +2101,10 @@ void Wasm2JSGlue::emitPreES6() { noteImport(import->module, import->base); }); ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { + // The scratch memory helpers are emitted in the glue, see code and comments below. + if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { + return; + } noteImport(import->module, import->base); }); @@ -2163,6 +2179,10 @@ void Wasm2JSGlue::emitPostES6() { out << "abort:function() { throw new Error('abort'); }"; ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { + // The scratch memory helpers are emitted in the glue, see code and comments below. + if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { + return; + } out << "," << import->base.str; }); out << "},mem" << moduleName.str << ");\n"; @@ -2234,6 +2254,83 @@ void Wasm2JSGlue::emitMemory(std::string buffer, std::string segmentWriter) { << "\");\n"; } } + +void Wasm2JSGlue::emitScratchMemorySupport() { + // The scratch memory helpers are emitted here the glue. We may also want to + // emit them inline at some point. (The reason they are imports is so that + // they appear as "intrinsics" placeholders, and not normal functions that + // the optimizer might want to do something with.) + bool needScratchMemory = false; + ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { + if (ABI::wasm2js::isScratchMemoryHelper(import->base)) { + needScratchMemory = true; + } + }); + if (!needScratchMemory) return; + + out << R"( + var scratchBuffer = new ArrayBuffer(8); + var i32ScratchView = new Int32Array(scratchBuffer); + var f32ScratchView = new Float32Array(scratchBuffer); + var f64ScratchView = new Float64Array(scratchBuffer); + )"; + + ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { + if (import->base == ABI::wasm2js::SCRATCH_STORE_I32) { + out << R"( + function wasm2js_scratch_store_i32(index, value) { + i32ScratchView[index] = value; + } + )"; + } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_I32) { + out << R"( + function wasm2js_scratch_load_i32(index) { + return i32ScratchView[index]; + } + )"; + } else if (import->base == ABI::wasm2js::SCRATCH_STORE_I64) { + out << R"( + function legalimport$wasm2js_scratch_store_i64(low, high) { + i32ScratchView[0] = low; + i32ScratchView[1] = high; + } + )"; + } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_I64) { + out << R"( + function legalimport$wasm2js_scratch_load_i64() { + if (typeof setTempRet0 === 'function') setTempRet0(i32ScratchView[1]); + return i32ScratchView[0]; + } + )"; + } else if (import->base == ABI::wasm2js::SCRATCH_STORE_F32) { + out << R"( + function wasm2js_scratch_store_f32(value) { + f32ScratchView[0] = value; + } + )"; + } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_F32) { + out << R"( + function wasm2js_scratch_load_f32() { + return f32ScratchView[0]; + } + )"; + } else if (import->base == ABI::wasm2js::SCRATCH_STORE_F64) { + out << R"( + function wasm2js_scratch_store_f64(value) { + f64ScratchView[0] = value; + } + )"; + } else if (import->base == ABI::wasm2js::SCRATCH_LOAD_F64) { + out << R"( + function wasm2js_scratch_load_f64() { + return f64ScratchView[0]; + } + )"; + } + }); + out << '\n'; +} + } // namespace wasm #endif // wasm_wasm2js_h |