| /* | |
| * Copyright 2017 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. | |
| */ | |
| // | |
| // Lowers i64s to i32s by splitting variables and arguments into pairs of i32s. | |
| // i64 return values are lowered by returning the low half and storing the high | |
| // half into a global. | |
| // | |
| // Note: This pass is designed primarily as an internal part of the wasm2js | |
| // pipeline rather than a general-purpose lowering pass for standard WebAssembly | |
| // runtimes. As such, it does not strictly preserve all WebAssembly trapping | |
| // semantics. In particular, float-to-int conversions are lowered using float | |
| // arithmetic and 32-bit truncations that do not trap on out-of-range values, | |
| // NaN, or infinity, relying on the fact that wasm2js maps 32-bit truncations | |
| // to non-trapping JavaScript bitwise operations (~~ and >>>). | |
| // | |
| #include "abi/js.h" | |
| #include "ir/flat.h" | |
| #include "ir/iteration.h" | |
| #include "ir/memory-utils.h" | |
| #include "ir/module-utils.h" | |
| #include "ir/names.h" | |
| #include "pass.h" | |
| #include "support/istring.h" | |
| #include "support/name.h" | |
| #include "wasm-builder.h" | |
| #include "wasm.h" | |
| #include <algorithm> | |
| namespace wasm { | |
| static Name makeHighName(Name n) { return n.toString() + "$hi"; } | |
| struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { | |
| // Adds calls to helper functions. | |
| bool addsEffects() override { return true; } | |
| struct TempVar { | |
| TempVar(Index idx, Type ty, I64ToI32Lowering& pass) | |
| : idx(idx), pass(pass), moved(false), ty(ty) {} | |
| TempVar(TempVar&& other) | |
| : idx(other), pass(other.pass), moved(false), ty(other.ty) { | |
| assert(!other.moved); | |
| other.moved = true; | |
| } | |
| TempVar& operator=(TempVar&& rhs) { | |
| assert(!rhs.moved); | |
| // free overwritten idx | |
| if (!moved) { | |
| freeIdx(); | |
| } | |
| idx = rhs.idx; | |
| rhs.moved = true; | |
| moved = false; | |
| return *this; | |
| } | |
| ~TempVar() { | |
| if (!moved) { | |
| freeIdx(); | |
| } | |
| } | |
| bool operator==(const TempVar& rhs) { | |
| assert(!moved && !rhs.moved); | |
| return idx == rhs.idx; | |
| } | |
| operator Index() { | |
| assert(!moved); | |
| return idx; | |
| } | |
| // disallow copying | |
| TempVar(const TempVar&) = delete; | |
| TempVar& operator=(const TempVar&) = delete; | |
| private: | |
| void freeIdx() { | |
| auto& freeList = pass.freeTemps[ty.getBasic()]; | |
| assert(std::find(freeList.begin(), freeList.end(), idx) == | |
| freeList.end()); | |
| freeList.push_back(idx); | |
| } | |
| Index idx; | |
| I64ToI32Lowering& pass; | |
| bool moved; // since C++ will still destruct moved-from values | |
| Type ty; | |
| }; | |
| // false since function types need to be lowered | |
| // TODO: allow module-level transformations in parallel passes | |
| bool isFunctionParallel() override { return false; } | |
| std::unique_ptr<Pass> create() override { | |
| return std::make_unique<I64ToI32Lowering>(); | |
| } | |
| void doWalkModule(Module* module) { | |
| if (!builder) { | |
| builder = std::make_unique<Builder>(*module); | |
| } | |
| // add new globals for high bits | |
| for (size_t i = 0, globals = module->globals.size(); i < globals; ++i) { | |
| auto* curr = module->globals[i].get(); | |
| if (curr->type != Type::i64) { | |
| continue; | |
| } | |
| originallyI64Globals.insert(curr->name); | |
| curr->type = Type::i32; | |
| auto high = builder->makeGlobal(makeHighName(curr->name), | |
| Type::i32, | |
| builder->makeConst(int32_t(0)), | |
| curr->mutable_ ? Builder::Mutable | |
| : Builder::Immutable); | |
| if (curr->imported()) { | |
| Fatal() << "TODO: imported i64 globals"; | |
| } else { | |
| if (auto* c = curr->init->dynCast<Const>()) { | |
| uint64_t value = c->value.geti64(); | |
| c->value = Literal(uint32_t(value)); | |
| c->type = Type::i32; | |
| high->init = builder->makeConst(uint32_t(value >> 32)); | |
| } else if (auto* get = curr->init->dynCast<GlobalGet>()) { | |
| high->init = | |
| builder->makeGlobalGet(makeHighName(get->name), Type::i32); | |
| } else { | |
| WASM_UNREACHABLE("unexpected expression type"); | |
| } | |
| curr->init->type = Type::i32; | |
| } | |
| module->addGlobal(std::move(high)); | |
| } | |
| // For functions that return 64-bit values, we use this global variable | |
| // to return the high 32 bits. | |
| auto* highBits = new Global(); | |
| highBits->type = Type::i32; | |
| highBits->name = INT64_TO_32_HIGH_BITS; | |
| highBits->init = builder->makeConst(int32_t(0)); | |
| highBits->mutable_ = true; | |
| module->addGlobal(highBits); | |
| PostWalker<I64ToI32Lowering>::doWalkModule(module); | |
| } | |
| void doWalkFunction(Function* func) { | |
| Flat::verifyFlatness(func); | |
| // create builder here if this is first entry to module for this object | |
| if (!builder) { | |
| builder = std::make_unique<Builder>(*getModule()); | |
| } | |
| indexMap.clear(); | |
| highBitVars.clear(); | |
| freeTemps.clear(); | |
| Module temp; | |
| auto* oldFunc = ModuleUtils::copyFunction(func, temp); | |
| func->setParams(Type::none); | |
| func->vars.clear(); | |
| func->localNames.clear(); | |
| func->localIndices.clear(); | |
| Index newIdx = 0; | |
| Names::ensureNames(oldFunc); | |
| for (Index i = 0; i < oldFunc->getNumLocals(); ++i) { | |
| assert(oldFunc->hasLocalName(i)); | |
| Name lowName = oldFunc->getLocalName(i); | |
| Name highName = makeHighName(lowName); | |
| Type paramType = oldFunc->getLocalType(i); | |
| auto builderFunc = | |
| (i < oldFunc->getVarIndexBase()) | |
| ? Builder::addParam | |
| : static_cast<Index (*)(Function*, Name, Type)>(Builder::addVar); | |
| if (paramType == Type::i64) { | |
| builderFunc(func, lowName, Type::i32); | |
| builderFunc(func, highName, Type::i32); | |
| indexMap[i] = newIdx; | |
| newIdx += 2; | |
| } else { | |
| builderFunc(func, lowName, paramType); | |
| indexMap[i] = newIdx++; | |
| } | |
| } | |
| nextTemp = func->getNumLocals(); | |
| PostWalker<I64ToI32Lowering>::doWalkFunction(func); | |
| } | |
| void visitFunction(Function* func) { | |
| if (func->imported()) { | |
| return; | |
| } | |
| if (func->getResults() == Type::i64) { | |
| func->setResults(Type::i32); | |
| // body may not have out param if it ends with control flow | |
| if (hasOutParam(func->body)) { | |
| TempVar highBits = fetchOutParam(func->body); | |
| TempVar lowBits = getTemp(); | |
| LocalSet* setLow = builder->makeLocalSet(lowBits, func->body); | |
| GlobalSet* setHigh = builder->makeGlobalSet( | |
| INT64_TO_32_HIGH_BITS, builder->makeLocalGet(highBits, Type::i32)); | |
| LocalGet* getLow = builder->makeLocalGet(lowBits, Type::i32); | |
| func->body = builder->blockify(setLow, setHigh, getLow); | |
| } | |
| } | |
| int idx = 0; | |
| for (size_t i = func->getNumLocals(); i < nextTemp; i++) { | |
| Name tmpName("i64toi32_i32$" + std::to_string(idx++)); | |
| builder->addVar(func, tmpName, tempTypes[i]); | |
| } | |
| } | |
| 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> | |
| T* visitGenericCall(T* curr, BuilderFunc<T> callBuilder) { | |
| if (handleUnreachable(curr)) { | |
| return nullptr; | |
| } | |
| 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->makeLocalGet(argHighBits, Type::i32)); | |
| fixed = true; | |
| } | |
| } | |
| if (curr->type != Type::i64) { | |
| auto* ret = callBuilder(args, curr->type); | |
| replaceCurrent(ret); | |
| return fixed ? ret : nullptr; | |
| } | |
| TempVar lowBits = getTemp(); | |
| TempVar highBits = getTemp(); | |
| auto* call = callBuilder(args, Type::i32); | |
| LocalSet* doCall = builder->makeLocalSet(lowBits, call); | |
| LocalSet* setHigh = builder->makeLocalSet( | |
| highBits, builder->makeGlobalGet(INT64_TO_32_HIGH_BITS, Type::i32)); | |
| LocalGet* getLow = builder->makeLocalGet(lowBits, Type::i32); | |
| Block* result = builder->blockify(doCall, setHigh, getLow); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| return call; | |
| } | |
| void visitCall(Call* curr) { | |
| if (curr->isReturn && | |
| getModule()->getFunction(curr->target)->getResults() == Type::i64) { | |
| Fatal() | |
| << "i64 to i32 lowering of return_call values not yet implemented"; | |
| } | |
| auto* fixedCall = visitGenericCall<Call>( | |
| curr, [&](std::vector<Expression*>& args, Type results) { | |
| return builder->makeCall(curr->target, args, results, curr->isReturn); | |
| }); | |
| // 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.toString(); | |
| return; | |
| } | |
| } | |
| void visitCallIndirect(CallIndirect* curr) { | |
| if (curr->isReturn && curr->heapType.getSignature().results == Type::i64) { | |
| Fatal() | |
| << "i64 to i32 lowering of return_call values not yet implemented"; | |
| } | |
| visitGenericCall<CallIndirect>( | |
| curr, [&](std::vector<Expression*>& args, Type results) { | |
| std::vector<Type> params; | |
| for (const auto& param : curr->heapType.getSignature().params) { | |
| if (param == Type::i64) { | |
| params.push_back(Type::i32); | |
| params.push_back(Type::i32); | |
| } else { | |
| params.push_back(param); | |
| } | |
| } | |
| return builder->makeCallIndirect(curr->table, | |
| curr->target, | |
| args, | |
| Signature(Type(params), results), | |
| curr->isReturn); | |
| }); | |
| } | |
| void visitRefFunc(RefFunc* curr) { | |
| auto sig = curr->type.getHeapType().getSignature(); | |
| bool hasI64Param = false; | |
| for (auto t : sig.params) { | |
| if (t == Type::i64) { | |
| hasI64Param = true; | |
| break; | |
| } | |
| } | |
| auto params = sig.params; | |
| if (hasI64Param) { | |
| std::vector<Type> newParams; | |
| for (auto t : sig.params) { | |
| if (t == Type::i64) { | |
| newParams.push_back(Type::i32); | |
| newParams.push_back(Type::i32); | |
| } else { | |
| newParams.push_back(t); | |
| } | |
| } | |
| params = Type(newParams); | |
| }; | |
| auto results = sig.results; | |
| // Update the results the same way we do when visiting functions. We use a | |
| // global rather than multivalue to lower i64 results. | |
| if (results == Type::i64) { | |
| results = Type::i32; | |
| } | |
| if (params != sig.params || results != sig.results) { | |
| curr->type = curr->type.with(HeapType(Signature(params, results))); | |
| } | |
| } | |
| void visitLocalGet(LocalGet* curr) { | |
| const auto mappedIndex = indexMap[curr->index]; | |
| // Need to remap the local into the new naming scheme, regardless of | |
| // the type of the local. | |
| curr->index = mappedIndex; | |
| if (curr->type != Type::i64) { | |
| return; | |
| } | |
| curr->type = Type::i32; | |
| TempVar highBits = getTemp(); | |
| LocalSet* setHighBits = builder->makeLocalSet( | |
| highBits, builder->makeLocalGet(mappedIndex + 1, Type::i32)); | |
| Block* result = builder->blockify(setHighBits, curr); | |
| replaceCurrent(result); | |
| setOutParam(result, std::move(highBits)); | |
| } | |
| void lowerTee(LocalSet* curr) { | |
| TempVar highBits = fetchOutParam(curr->value); | |
| TempVar tmp = getTemp(); | |
| curr->type = Type::i32; | |
| LocalSet* setLow = builder->makeLocalSet(tmp, curr); | |
| LocalSet* setHigh = builder->makeLocalSet( | |
| curr->index + 1, builder->makeLocalGet(highBits, Type::i32)); | |
| LocalGet* getLow = builder->makeLocalGet(tmp, Type::i32); | |
| Block* result = builder->blockify(setLow, setHigh, getLow); | |
| replaceCurrent(result); | |
| setOutParam(result, std::move(highBits)); | |
| } | |
| void visitLocalSet(LocalSet* curr) { | |
| const auto mappedIndex = indexMap[curr->index]; | |
| // Need to remap the local into the new naming scheme, regardless of | |
| // the type of the local. Note that lowerTee depends on this happening. | |
| curr->index = mappedIndex; | |
| if (!hasOutParam(curr->value)) { | |
| return; | |
| } | |
| if (curr->isTee()) { | |
| lowerTee(curr); | |
| return; | |
| } | |
| TempVar highBits = fetchOutParam(curr->value); | |
| auto* setHigh = builder->makeLocalSet( | |
| mappedIndex + 1, builder->makeLocalGet(highBits, Type::i32)); | |
| Block* result = builder->blockify(curr, setHigh); | |
| replaceCurrent(result); | |
| } | |
| void visitGlobalGet(GlobalGet* curr) { | |
| if (!getFunction()) { | |
| return; // if in a global init, skip - we already handled that. | |
| } | |
| if (!originallyI64Globals.contains(curr->name)) { | |
| return; | |
| } | |
| curr->type = Type::i32; | |
| TempVar highBits = getTemp(); | |
| LocalSet* setHighBits = builder->makeLocalSet( | |
| highBits, builder->makeGlobalGet(makeHighName(curr->name), Type::i32)); | |
| Block* result = builder->blockify(setHighBits, curr); | |
| replaceCurrent(result); | |
| setOutParam(result, std::move(highBits)); | |
| } | |
| void visitGlobalSet(GlobalSet* curr) { | |
| if (!originallyI64Globals.contains(curr->name)) { | |
| return; | |
| } | |
| if (handleUnreachable(curr)) { | |
| return; | |
| } | |
| TempVar highBits = fetchOutParam(curr->value); | |
| auto* setHigh = builder->makeGlobalSet( | |
| makeHighName(curr->name), builder->makeLocalGet(highBits, Type::i32)); | |
| replaceCurrent(builder->makeSequence(curr, setHigh)); | |
| } | |
| void visitLoad(Load* curr) { | |
| if (curr->type != Type::i64) { | |
| return; | |
| } | |
| assert(!curr->isAtomic() && "64-bit atomic load not implemented"); | |
| TempVar lowBits = getTemp(); | |
| TempVar highBits = getTemp(); | |
| TempVar ptrTemp = getTemp(); | |
| LocalSet* setPtr = builder->makeLocalSet(ptrTemp, curr->ptr); | |
| LocalSet* loadHigh; | |
| if (curr->bytes == 8) { | |
| loadHigh = builder->makeLocalSet( | |
| highBits, | |
| builder->makeLoad(4, | |
| curr->signed_, | |
| curr->offset + 4, | |
| std::min(uint32_t(curr->align), uint32_t(4)), | |
| builder->makeLocalGet(ptrTemp, Type::i32), | |
| Type::i32, | |
| curr->memory)); | |
| } else if (curr->signed_) { | |
| loadHigh = builder->makeLocalSet( | |
| highBits, | |
| builder->makeBinary(ShrSInt32, | |
| builder->makeLocalGet(lowBits, Type::i32), | |
| builder->makeConst(int32_t(31)))); | |
| } else { | |
| loadHigh = | |
| builder->makeLocalSet(highBits, builder->makeConst(int32_t(0))); | |
| } | |
| curr->type = Type::i32; | |
| curr->bytes = std::min(curr->bytes, uint8_t(4)); | |
| curr->align = std::min(uint32_t(curr->align), uint32_t(4)); | |
| curr->ptr = builder->makeLocalGet(ptrTemp, Type::i32); | |
| Block* result = | |
| builder->blockify(setPtr, | |
| builder->makeLocalSet(lowBits, curr), | |
| loadHigh, | |
| builder->makeLocalGet(lowBits, Type::i32)); | |
| replaceCurrent(result); | |
| setOutParam(result, std::move(highBits)); | |
| } | |
| void visitStore(Store* curr) { | |
| if (!hasOutParam(curr->value)) { | |
| return; | |
| } | |
| assert(curr->offset + 4 > curr->offset); | |
| assert(!curr->isAtomic() && "atomic store not implemented"); | |
| TempVar highBits = fetchOutParam(curr->value); | |
| uint8_t bytes = curr->bytes; | |
| curr->bytes = std::min(curr->bytes, uint8_t(4)); | |
| curr->align = std::min(uint32_t(curr->align), uint32_t(4)); | |
| curr->valueType = Type::i32; | |
| if (bytes == 8) { | |
| TempVar ptrTemp = getTemp(); | |
| LocalSet* setPtr = builder->makeLocalSet(ptrTemp, curr->ptr); | |
| curr->ptr = builder->makeLocalGet(ptrTemp, Type::i32); | |
| curr->finalize(); | |
| Store* storeHigh = | |
| builder->makeStore(4, | |
| curr->offset + 4, | |
| std::min(uint32_t(curr->align), uint32_t(4)), | |
| builder->makeLocalGet(ptrTemp, Type::i32), | |
| builder->makeLocalGet(highBits, Type::i32), | |
| Type::i32, | |
| curr->memory); | |
| replaceCurrent(builder->blockify(setPtr, curr, storeHigh)); | |
| } | |
| } | |
| void visitAtomicRMW(AtomicRMW* curr) { | |
| if (handleUnreachable(curr)) { | |
| return; | |
| } | |
| if (curr->type != Type::i64) { | |
| return; | |
| } | |
| // We cannot break this up into smaller operations as it must be atomic. | |
| // Lower to an intrinsic function that wasm2js will implement. | |
| TempVar lowBits = getTemp(); | |
| TempVar highBits = getTemp(); | |
| auto* getLow = builder->makeCall( | |
| ABI::wasm2js::ATOMIC_RMW_I64, | |
| {builder->makeConst(int32_t(curr->op)), | |
| builder->makeConst(int32_t(curr->bytes)), | |
| builder->makeConst(int32_t(curr->offset)), | |
| curr->ptr, | |
| curr->value, | |
| builder->makeLocalGet(fetchOutParam(curr->value), Type::i32)}, | |
| Type::i32); | |
| auto* getHigh = | |
| builder->makeCall(ABI::wasm2js::GET_STASHED_BITS, {}, Type::i32); | |
| auto* setLow = builder->makeLocalSet(lowBits, getLow); | |
| auto* setHigh = builder->makeLocalSet(highBits, getHigh); | |
| auto* finalGet = builder->makeLocalGet(lowBits, Type::i32); | |
| auto* result = builder->makeBlock({setLow, setHigh, finalGet}); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| } | |
| void visitAtomicCmpxchg(AtomicCmpxchg* curr) { | |
| assert(curr->type != Type::i64 && "64-bit AtomicCmpxchg not implemented"); | |
| } | |
| void visitAtomicWait(AtomicWait* curr) { | |
| // The last parameter is an i64, so we cannot leave it as it is | |
| replaceCurrent(builder->makeCall( | |
| ABI::wasm2js::ATOMIC_WAIT_I32, | |
| {builder->makeConst(int32_t(curr->offset)), | |
| curr->ptr, | |
| curr->expected, | |
| curr->timeout, | |
| builder->makeLocalGet(fetchOutParam(curr->timeout), Type::i32)}, | |
| Type::i32)); | |
| } | |
| void visitConst(Const* curr) { | |
| if (!getFunction()) { | |
| return; // if in a global init, skip - we already handled that. | |
| } | |
| if (curr->type != Type::i64) { | |
| return; | |
| } | |
| TempVar highBits = getTemp(); | |
| Const* lowVal = | |
| builder->makeConst(int32_t(curr->value.geti64() & 0xffffffff)); | |
| LocalSet* setHigh = builder->makeLocalSet( | |
| highBits, | |
| builder->makeConst(int32_t(uint64_t(curr->value.geti64()) >> 32))); | |
| Block* result = builder->blockify(setHigh, lowVal); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| } | |
| void lowerEqZInt64(Unary* curr) { | |
| TempVar highBits = fetchOutParam(curr->value); | |
| auto* result = builder->makeUnary( | |
| EqZInt32, | |
| builder->makeBinary( | |
| OrInt32, curr->value, builder->makeLocalGet(highBits, Type::i32))); | |
| replaceCurrent(result); | |
| } | |
| void lowerExtendUInt32(Unary* curr) { | |
| TempVar highBits = getTemp(); | |
| Block* result = builder->blockify( | |
| builder->makeLocalSet(highBits, builder->makeConst(int32_t(0))), | |
| curr->value); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| } | |
| void lowerExtendSInt32(Unary* curr) { | |
| TempVar highBits = getTemp(); | |
| TempVar lowBits = getTemp(); | |
| LocalSet* setLow = builder->makeLocalSet(lowBits, curr->value); | |
| LocalSet* setHigh = builder->makeLocalSet( | |
| highBits, | |
| builder->makeBinary(ShrSInt32, | |
| builder->makeLocalGet(lowBits, Type::i32), | |
| builder->makeConst(int32_t(31)))); | |
| Block* result = builder->blockify( | |
| setLow, setHigh, builder->makeLocalGet(lowBits, Type::i32)); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| } | |
| void lowerExtendSInt64(Unary* curr) { | |
| TempVar highBits = getTemp(); | |
| TempVar lowBits = getTemp(); | |
| // free the temp var | |
| fetchOutParam(curr->value); | |
| Expression* lowValue = curr->value; | |
| switch (curr->op) { | |
| case ExtendS8Int64: | |
| lowValue = builder->makeUnary(ExtendS8Int32, lowValue); | |
| break; | |
| case ExtendS16Int64: | |
| lowValue = builder->makeUnary(ExtendS16Int32, lowValue); | |
| break; | |
| default: | |
| break; | |
| } | |
| LocalSet* setLow = builder->makeLocalSet(lowBits, lowValue); | |
| LocalSet* setHigh = builder->makeLocalSet( | |
| highBits, | |
| builder->makeBinary(ShrSInt32, | |
| builder->makeLocalGet(lowBits, Type::i32), | |
| builder->makeConst(int32_t(31)))); | |
| Block* result = builder->blockify( | |
| setLow, setHigh, builder->makeLocalGet(lowBits, Type::i32)); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| } | |
| void lowerWrapInt64(Unary* curr) { | |
| // free the temp var | |
| fetchOutParam(curr->value); | |
| replaceCurrent(curr->value); | |
| } | |
| void lowerReinterpretFloat64(Unary* curr) { | |
| // Roundtrip the f64 through a scratch buffer via wasm2js helper functions | |
| // to extract the low and high 32-bit integer halves. | |
| TempVar highBits = getTemp(); | |
| Block* result = builder->blockify( | |
| builder->makeCall( | |
| ABI::wasm2js::SCRATCH_STORE_F64, {curr->value}, Type::none), | |
| builder->makeLocalSet(highBits, | |
| builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, | |
| {builder->makeConst(int32_t(1))}, | |
| Type::i32)), | |
| builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_I32, | |
| {builder->makeConst(int32_t(0))}, | |
| Type::i32)); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| MemoryUtils::ensureExists(getModule()); | |
| ABI::wasm2js::ensureHelpers(getModule()); | |
| } | |
| void lowerReinterpretInt64(Unary* curr) { | |
| // Roundtrip the low and high 32-bit integer halves through a scratch buffer | |
| // via wasm2js helper functions to reconstruct the f64 value. | |
| TempVar highBits = fetchOutParam(curr->value); | |
| Block* result = builder->blockify( | |
| builder->makeCall(ABI::wasm2js::SCRATCH_STORE_I32, | |
| {builder->makeConst(int32_t(0)), curr->value}, | |
| Type::none), | |
| builder->makeCall(ABI::wasm2js::SCRATCH_STORE_I32, | |
| {builder->makeConst(int32_t(1)), | |
| builder->makeLocalGet(highBits, Type::i32)}, | |
| Type::none), | |
| builder->makeCall(ABI::wasm2js::SCRATCH_LOAD_F64, {}, Type::f64)); | |
| replaceCurrent(result); | |
| MemoryUtils::ensureExists(getModule()); | |
| ABI::wasm2js::ensureHelpers(getModule()); | |
| } | |
| void lowerTruncFloatToInt(Unary* curr) { | |
| // Lowers 64-bit float-to-int truncations into 32-bit float arithmetic and | |
| // 32-bit truncations. | |
| // | |
| // Note that this lowering is non-trapping: in wasm2js, the emitted | |
| // 32-bit truncations are translated to JavaScript bitwise operations | |
| // ((~~expr) >>> 0), so out-of-range values, NaN, and +/-infinity do not | |
| // trap. Both signed and unsigned operations share the same logic because | |
| // the two's complement bit representation is identical for in-range values. | |
| // | |
| // Pseudocode: | |
| // hiBits = if abs(f) >= 1.0 { | |
| // if f > 0.0 { | |
| // (unsigned) min( | |
| // floor(f / (float) U32_MAX), | |
| // (float) U32_MAX - 1, | |
| // ) | |
| // } else { | |
| // (unsigned) ceil((f - (float) (unsigned) f) / ((float) U32_MAX)) | |
| // } | |
| // } else { | |
| // 0 | |
| // } | |
| // | |
| // loBits = (unsigned) f; | |
| Literal litZero, litOne, u32Max; | |
| UnaryOp trunc, convert, abs, floor, ceil; | |
| Type localType; | |
| BinaryOp ge, gt, min, div, sub; | |
| switch (curr->op) { | |
| case TruncSFloat32ToInt64: | |
| case TruncSatSFloat32ToInt64: | |
| case TruncUFloat32ToInt64: | |
| case TruncSatUFloat32ToInt64: { | |
| litZero = Literal((float)0); | |
| litOne = Literal((float)1); | |
| u32Max = Literal(((float)UINT_MAX) + 1); | |
| trunc = TruncUFloat32ToInt32; | |
| convert = ConvertUInt32ToFloat32; | |
| localType = Type::f32; | |
| abs = AbsFloat32; | |
| ge = GeFloat32; | |
| gt = GtFloat32; | |
| min = MinFloat32; | |
| floor = FloorFloat32; | |
| ceil = CeilFloat32; | |
| div = DivFloat32; | |
| sub = SubFloat32; | |
| break; | |
| } | |
| case TruncSFloat64ToInt64: | |
| case TruncSatSFloat64ToInt64: | |
| case TruncUFloat64ToInt64: | |
| case TruncSatUFloat64ToInt64: { | |
| litZero = Literal((double)0); | |
| litOne = Literal((double)1); | |
| u32Max = Literal(((double)UINT_MAX) + 1); | |
| trunc = TruncUFloat64ToInt32; | |
| convert = ConvertUInt32ToFloat64; | |
| localType = Type::f64; | |
| abs = AbsFloat64; | |
| ge = GeFloat64; | |
| gt = GtFloat64; | |
| min = MinFloat64; | |
| floor = FloorFloat64; | |
| ceil = CeilFloat64; | |
| div = DivFloat64; | |
| sub = SubFloat64; | |
| break; | |
| } | |
| default: | |
| abort(); | |
| } | |
| TempVar f = getTemp(localType); | |
| TempVar highBits = getTemp(); | |
| Expression* gtZeroBranch = builder->makeBinary( | |
| min, | |
| builder->makeUnary( | |
| floor, | |
| builder->makeBinary(div, | |
| builder->makeLocalGet(f, localType), | |
| builder->makeConst(u32Max))), | |
| builder->makeBinary( | |
| sub, builder->makeConst(u32Max), builder->makeConst(litOne))); | |
| Expression* ltZeroBranch = builder->makeUnary( | |
| ceil, | |
| builder->makeBinary( | |
| div, | |
| builder->makeBinary( | |
| sub, | |
| builder->makeLocalGet(f, localType), | |
| builder->makeUnary( | |
| convert, | |
| builder->makeUnary(trunc, builder->makeLocalGet(f, localType)))), | |
| builder->makeConst(u32Max))); | |
| If* highBitsCalc = builder->makeIf( | |
| builder->makeBinary( | |
| gt, builder->makeLocalGet(f, localType), builder->makeConst(litZero)), | |
| builder->makeUnary(trunc, gtZeroBranch), | |
| builder->makeUnary(trunc, ltZeroBranch)); | |
| If* highBitsVal = builder->makeIf( | |
| builder->makeBinary( | |
| ge, | |
| builder->makeUnary(abs, builder->makeLocalGet(f, localType)), | |
| builder->makeConst(litOne)), | |
| highBitsCalc, | |
| builder->makeConst(int32_t(0))); | |
| Block* result = builder->blockify( | |
| builder->makeLocalSet(f, curr->value), | |
| builder->makeLocalSet(highBits, highBitsVal), | |
| builder->makeUnary(trunc, builder->makeLocalGet(f, localType))); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| } | |
| void lowerConvertIntToFloat(Unary* curr) { | |
| // Here the same strategy as `emcc` is taken which takes the two halves of | |
| // the 64-bit integer and creates a mathematical expression using float | |
| // arithmetic to reassemble the final floating point value. | |
| // | |
| // For example for i64 -> f32 we generate: | |
| // | |
| // ((double) (unsigned) lowBits) + | |
| // ((double) U32_MAX) * ((double) (int) highBits) | |
| // | |
| // Mostly just shuffling things around here with coercions and whatnot! | |
| // Note though that all arithmetic is done with f64 to have as much | |
| // precision as we can. | |
| // | |
| // NB: this is *not* accurate for i64 -> f32. Using f64s for intermediate | |
| // operations can give slightly inaccurate results in some cases, as we | |
| // round to an f64, then round again to an f32, which is not always the | |
| // same as a single rounding of i64 to f32 directly. Example: | |
| // | |
| // #include <stdio.h> | |
| // int main() { | |
| // unsigned long long x = 18446743523953737727ULL; | |
| // float y = x; | |
| // double z = x; | |
| // float w = z; | |
| // printf("i64 : %llu\n" | |
| // "i64->f32 : %f\n" | |
| // "i64->f64 : %f\n" | |
| // "i64->f64->f32: %f\n", x, y, z, w); | |
| // } | |
| // | |
| // i64 : 18446743523953737727 | |
| // i64->f32 : 18446742974197923840.000000 ;; correct rounding to f32 | |
| // i64->f64 : 18446743523953737728.000000 ;; correct rounding to f64 | |
| // i64->f64->f32: 18446744073709551616.000000 ;; incorrect rounding to f32 | |
| // | |
| // This is even a problem if we use BigInts in JavaScript to represent | |
| // i64s, as Math.fround(BigInt) is not supported - the BigInt must be | |
| // converted to a Number first, so we again have that extra rounding. | |
| // | |
| // A more precise approach could use compiled floatdisf/floatundisf from | |
| // compiler-rt, but that is much larger and slower. (Note that we are in the | |
| // interesting situation of having f32 and f64 operations and only missing | |
| // i64 ones, so we have a different problem to solve than compiler-rt, and | |
| // maybe there is a better solution we haven't found yet.) | |
| TempVar highBits = fetchOutParam(curr->value); | |
| TempVar lowBits = getTemp(); | |
| TempVar highResult = getTemp(); | |
| UnaryOp convertHigh; | |
| switch (curr->op) { | |
| case ConvertSInt64ToFloat32: | |
| case ConvertSInt64ToFloat64: | |
| convertHigh = ConvertSInt32ToFloat64; | |
| break; | |
| case ConvertUInt64ToFloat32: | |
| case ConvertUInt64ToFloat64: | |
| convertHigh = ConvertUInt32ToFloat64; | |
| break; | |
| default: | |
| abort(); | |
| } | |
| Expression* result = builder->blockify( | |
| builder->makeLocalSet(lowBits, curr->value), | |
| builder->makeLocalSet(highResult, builder->makeConst(int32_t(0))), | |
| builder->makeBinary( | |
| AddFloat64, | |
| builder->makeUnary(ConvertUInt32ToFloat64, | |
| builder->makeLocalGet(lowBits, Type::i32)), | |
| builder->makeBinary( | |
| MulFloat64, | |
| builder->makeConst((double)UINT_MAX + 1), | |
| builder->makeUnary(convertHigh, | |
| builder->makeLocalGet(highBits, Type::i32))))); | |
| switch (curr->op) { | |
| case ConvertSInt64ToFloat32: | |
| case ConvertUInt64ToFloat32: { | |
| result = builder->makeUnary(DemoteFloat64, result); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| replaceCurrent(result); | |
| } | |
| void lowerCountZeros(Unary* curr) { | |
| auto lower = [&](Block* result, | |
| UnaryOp op32, | |
| TempVar&& first, | |
| TempVar&& second) { | |
| TempVar highResult = getTemp(); | |
| TempVar firstResult = getTemp(); | |
| LocalSet* setFirst = builder->makeLocalSet( | |
| firstResult, | |
| builder->makeUnary(op32, builder->makeLocalGet(first, Type::i32))); | |
| Binary* check = | |
| builder->makeBinary(EqInt32, | |
| builder->makeLocalGet(firstResult, Type::i32), | |
| builder->makeConst(int32_t(32))); | |
| If* conditional = builder->makeIf( | |
| check, | |
| builder->makeBinary( | |
| AddInt32, | |
| builder->makeUnary(op32, builder->makeLocalGet(second, Type::i32)), | |
| builder->makeConst(int32_t(32))), | |
| builder->makeLocalGet(firstResult, Type::i32)); | |
| LocalSet* setHigh = | |
| builder->makeLocalSet(highResult, builder->makeConst(int32_t(0))); | |
| setOutParam(result, std::move(highResult)); | |
| replaceCurrent(builder->blockify(result, setFirst, setHigh, conditional)); | |
| }; | |
| TempVar highBits = fetchOutParam(curr->value); | |
| TempVar lowBits = getTemp(); | |
| LocalSet* setLow = builder->makeLocalSet(lowBits, curr->value); | |
| Block* result = builder->blockify(setLow); | |
| switch (curr->op) { | |
| case ClzInt64: | |
| lower(result, ClzInt32, std::move(highBits), std::move(lowBits)); | |
| break; | |
| case CtzInt64: | |
| WASM_UNREACHABLE("i64.ctz should be removed already"); | |
| break; | |
| default: | |
| abort(); | |
| } | |
| } | |
| bool unaryNeedsLowering(UnaryOp op) { | |
| switch (op) { | |
| case ClzInt64: | |
| case CtzInt64: | |
| case PopcntInt64: | |
| case EqZInt64: | |
| case ExtendSInt32: | |
| case ExtendUInt32: | |
| case WrapInt64: | |
| case TruncSFloat32ToInt64: | |
| case TruncSatSFloat32ToInt64: | |
| case TruncUFloat32ToInt64: | |
| case TruncSatUFloat32ToInt64: | |
| case TruncSFloat64ToInt64: | |
| case TruncSatSFloat64ToInt64: | |
| case TruncUFloat64ToInt64: | |
| case TruncSatUFloat64ToInt64: | |
| case ReinterpretFloat64: | |
| case ConvertSInt64ToFloat32: | |
| case ConvertSInt64ToFloat64: | |
| case ConvertUInt64ToFloat32: | |
| case ConvertUInt64ToFloat64: | |
| case ReinterpretInt64: | |
| case ExtendS8Int64: | |
| case ExtendS16Int64: | |
| case ExtendS32Int64: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| void visitUnary(Unary* curr) { | |
| if (!unaryNeedsLowering(curr->op)) { | |
| return; | |
| } | |
| if (handleUnreachable(curr)) { | |
| return; | |
| } | |
| assert(hasOutParam(curr->value) || curr->type == Type::i64 || | |
| curr->type == Type::f64); | |
| switch (curr->op) { | |
| case ClzInt64: | |
| case CtzInt64: | |
| lowerCountZeros(curr); | |
| break; | |
| case EqZInt64: | |
| lowerEqZInt64(curr); | |
| break; | |
| case ExtendSInt32: | |
| lowerExtendSInt32(curr); | |
| break; | |
| case ExtendUInt32: | |
| lowerExtendUInt32(curr); | |
| break; | |
| case WrapInt64: | |
| lowerWrapInt64(curr); | |
| break; | |
| case ReinterpretFloat64: | |
| lowerReinterpretFloat64(curr); | |
| break; | |
| case ReinterpretInt64: | |
| lowerReinterpretInt64(curr); | |
| break; | |
| case TruncSFloat32ToInt64: | |
| case TruncSatSFloat32ToInt64: | |
| case TruncUFloat32ToInt64: | |
| case TruncSatUFloat32ToInt64: | |
| case TruncSFloat64ToInt64: | |
| case TruncSatSFloat64ToInt64: | |
| case TruncUFloat64ToInt64: | |
| case TruncSatUFloat64ToInt64: | |
| lowerTruncFloatToInt(curr); | |
| break; | |
| case ConvertSInt64ToFloat32: | |
| case ConvertSInt64ToFloat64: | |
| case ConvertUInt64ToFloat32: | |
| case ConvertUInt64ToFloat64: | |
| lowerConvertIntToFloat(curr); | |
| break; | |
| case ExtendS8Int64: | |
| case ExtendS16Int64: | |
| case ExtendS32Int64: | |
| lowerExtendSInt64(curr); | |
| break; | |
| case PopcntInt64: | |
| WASM_UNREACHABLE("i64.popcnt should already be removed"); | |
| default: | |
| std::cerr << "Unhandled unary operator: " << curr->op << std::endl; | |
| abort(); | |
| } | |
| } | |
| Block* lowerAdd(Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| TempVar lowResult = getTemp(); | |
| TempVar highResult = getTemp(); | |
| LocalSet* addLow = builder->makeLocalSet( | |
| lowResult, | |
| builder->makeBinary(AddInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32))); | |
| LocalSet* addHigh = builder->makeLocalSet( | |
| highResult, | |
| builder->makeBinary(AddInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32))); | |
| LocalSet* carryBit = builder->makeLocalSet( | |
| highResult, | |
| builder->makeBinary(AddInt32, | |
| builder->makeLocalGet(highResult, Type::i32), | |
| builder->makeConst(int32_t(1)))); | |
| If* checkOverflow = builder->makeIf( | |
| builder->makeBinary(LtUInt32, | |
| builder->makeLocalGet(lowResult, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32)), | |
| carryBit); | |
| LocalGet* getLow = builder->makeLocalGet(lowResult, Type::i32); | |
| result = builder->blockify(result, addLow, addHigh, checkOverflow, getLow); | |
| setOutParam(result, std::move(highResult)); | |
| return result; | |
| } | |
| Block* lowerSub(Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| TempVar lowResult = getTemp(); | |
| TempVar highResult = getTemp(); | |
| TempVar borrow = getTemp(); | |
| LocalSet* subLow = builder->makeLocalSet( | |
| lowResult, | |
| builder->makeBinary(SubInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32))); | |
| LocalSet* borrowBit = builder->makeLocalSet( | |
| borrow, | |
| builder->makeBinary(LtUInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32))); | |
| LocalSet* subHigh1 = builder->makeLocalSet( | |
| highResult, | |
| builder->makeBinary(AddInt32, | |
| builder->makeLocalGet(borrow, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32))); | |
| LocalSet* subHigh2 = builder->makeLocalSet( | |
| highResult, | |
| builder->makeBinary(SubInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(highResult, Type::i32))); | |
| LocalGet* getLow = builder->makeLocalGet(lowResult, Type::i32); | |
| result = | |
| builder->blockify(result, subLow, borrowBit, subHigh1, subHigh2, getLow); | |
| setOutParam(result, std::move(highResult)); | |
| return result; | |
| } | |
| Block* lowerBitwise(BinaryOp op, | |
| Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| BinaryOp op32; | |
| switch (op) { | |
| case AndInt64: | |
| op32 = AndInt32; | |
| break; | |
| case OrInt64: | |
| op32 = OrInt32; | |
| break; | |
| case XorInt64: | |
| op32 = XorInt32; | |
| break; | |
| default: | |
| abort(); | |
| } | |
| result = builder->blockify( | |
| result, | |
| builder->makeLocalSet( | |
| rightHigh, | |
| builder->makeBinary(op32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32))), | |
| builder->makeBinary(op32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32))); | |
| setOutParam(result, std::move(rightHigh)); | |
| return result; | |
| } | |
| Block* makeLargeShl(Index highBits, Index leftLow, Index shift) { | |
| return builder->blockify( | |
| builder->makeLocalSet( | |
| highBits, | |
| builder->makeBinary(ShlInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32))), | |
| builder->makeConst(int32_t(0))); | |
| } | |
| // a >> b where `b` >= 32 | |
| // | |
| // implement as: | |
| // | |
| // hi = leftHigh >> 31 // copy sign bit | |
| // lo = leftHigh >> (b - 32) | |
| Block* makeLargeShrS(Index highBits, Index leftHigh, Index shift) { | |
| return builder->blockify( | |
| builder->makeLocalSet( | |
| highBits, | |
| builder->makeBinary(ShrSInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeConst(int32_t(31)))), | |
| builder->makeBinary(ShrSInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32))); | |
| } | |
| Block* makeLargeShrU(Index highBits, Index leftHigh, Index shift) { | |
| return builder->blockify( | |
| builder->makeLocalSet(highBits, builder->makeConst(int32_t(0))), | |
| builder->makeBinary(ShrUInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32))); | |
| } | |
| Block* makeSmallShl(Index highBits, | |
| Index leftLow, | |
| Index leftHigh, | |
| Index shift, | |
| Binary* shiftMask, | |
| Binary* widthLessShift) { | |
| Binary* shiftedInBits = builder->makeBinary( | |
| AndInt32, | |
| shiftMask, | |
| builder->makeBinary( | |
| ShrUInt32, builder->makeLocalGet(leftLow, Type::i32), widthLessShift)); | |
| Binary* shiftHigh = | |
| builder->makeBinary(ShlInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32)); | |
| return builder->blockify( | |
| builder->makeLocalSet( | |
| highBits, builder->makeBinary(OrInt32, shiftedInBits, shiftHigh)), | |
| builder->makeBinary(ShlInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32))); | |
| } | |
| // a >> b where `b` < 32 | |
| // | |
| // implement as: | |
| // | |
| // hi = leftHigh >> b | |
| // lo = (leftLow >>> b) | (leftHigh << (32 - b)) | |
| Block* makeSmallShrS(Index highBits, | |
| Index leftLow, | |
| Index leftHigh, | |
| Index shift, | |
| Binary* shiftMask, | |
| Binary* widthLessShift) { | |
| Binary* shiftedInBits = builder->makeBinary( | |
| ShlInt32, | |
| builder->makeBinary( | |
| AndInt32, shiftMask, builder->makeLocalGet(leftHigh, Type::i32)), | |
| widthLessShift); | |
| Binary* shiftLow = | |
| builder->makeBinary(ShrUInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32)); | |
| return builder->blockify( | |
| builder->makeLocalSet( | |
| highBits, | |
| builder->makeBinary(ShrSInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32))), | |
| builder->makeBinary(OrInt32, shiftedInBits, shiftLow)); | |
| } | |
| Block* makeSmallShrU(Index highBits, | |
| Index leftLow, | |
| Index leftHigh, | |
| Index shift, | |
| Binary* shiftMask, | |
| Binary* widthLessShift) { | |
| Binary* shiftedInBits = builder->makeBinary( | |
| ShlInt32, | |
| builder->makeBinary( | |
| AndInt32, shiftMask, builder->makeLocalGet(leftHigh, Type::i32)), | |
| widthLessShift); | |
| Binary* shiftLow = | |
| builder->makeBinary(ShrUInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32)); | |
| return builder->blockify( | |
| builder->makeLocalSet( | |
| highBits, | |
| builder->makeBinary(ShrUInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(shift, Type::i32))), | |
| builder->makeBinary(OrInt32, shiftedInBits, shiftLow)); | |
| } | |
| Block* lowerShift(BinaryOp op, | |
| Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| assert(op == ShlInt64 || op == ShrUInt64 || op == ShrSInt64); | |
| // shift left lowered as: | |
| // if 32 <= rightLow % 64: | |
| // high = leftLow << k; low = 0 | |
| // else: | |
| // high = (((1 << k) - 1) & (leftLow >> (32 - k))) | (leftHigh << k); | |
| // low = leftLow << k | |
| // where k = shift % 32. shift right is similar. | |
| TempVar shift = getTemp(); | |
| LocalSet* setShift = builder->makeLocalSet( | |
| shift, | |
| builder->makeBinary(AndInt32, | |
| builder->makeLocalGet(rightLow, Type::i32), | |
| builder->makeConst(int32_t(32 - 1)))); | |
| Binary* isLargeShift = builder->makeBinary( | |
| LeUInt32, | |
| builder->makeConst(int32_t(32)), | |
| builder->makeBinary(AndInt32, | |
| builder->makeLocalGet(rightLow, Type::i32), | |
| builder->makeConst(int32_t(64 - 1)))); | |
| Block* largeShiftBlock; | |
| switch (op) { | |
| case ShlInt64: | |
| largeShiftBlock = makeLargeShl(rightHigh, leftLow, shift); | |
| break; | |
| case ShrSInt64: | |
| largeShiftBlock = makeLargeShrS(rightHigh, leftHigh, shift); | |
| break; | |
| case ShrUInt64: | |
| largeShiftBlock = makeLargeShrU(rightHigh, leftHigh, shift); | |
| break; | |
| default: | |
| abort(); | |
| } | |
| Binary* shiftMask = builder->makeBinary( | |
| SubInt32, | |
| builder->makeBinary(ShlInt32, | |
| builder->makeConst(int32_t(1)), | |
| builder->makeLocalGet(shift, Type::i32)), | |
| builder->makeConst(int32_t(1))); | |
| Binary* widthLessShift = | |
| builder->makeBinary(SubInt32, | |
| builder->makeConst(int32_t(32)), | |
| builder->makeLocalGet(shift, Type::i32)); | |
| Block* smallShiftBlock; | |
| switch (op) { | |
| case ShlInt64: { | |
| smallShiftBlock = makeSmallShl( | |
| rightHigh, leftLow, leftHigh, shift, shiftMask, widthLessShift); | |
| break; | |
| } | |
| case ShrSInt64: { | |
| smallShiftBlock = makeSmallShrS( | |
| rightHigh, leftLow, leftHigh, shift, shiftMask, widthLessShift); | |
| break; | |
| } | |
| case ShrUInt64: { | |
| smallShiftBlock = makeSmallShrU( | |
| rightHigh, leftLow, leftHigh, shift, shiftMask, widthLessShift); | |
| break; | |
| } | |
| default: | |
| abort(); | |
| } | |
| If* ifLargeShift = | |
| builder->makeIf(isLargeShift, largeShiftBlock, smallShiftBlock); | |
| result = builder->blockify(result, setShift, ifLargeShift); | |
| setOutParam(result, std::move(rightHigh)); | |
| return result; | |
| } | |
| Block* lowerEq(Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| return builder->blockify( | |
| result, | |
| builder->makeBinary( | |
| AndInt32, | |
| builder->makeBinary(EqInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32)), | |
| builder->makeBinary(EqInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32)))); | |
| } | |
| Block* lowerNe(Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| return builder->blockify( | |
| result, | |
| builder->makeBinary( | |
| OrInt32, | |
| builder->makeBinary(NeInt32, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32)), | |
| builder->makeBinary(NeInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32)))); | |
| } | |
| Block* lowerUComp(BinaryOp op, | |
| Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| BinaryOp highOp, lowOp; | |
| switch (op) { | |
| case LtUInt64: | |
| highOp = LtUInt32; | |
| lowOp = LtUInt32; | |
| break; | |
| case LeUInt64: | |
| highOp = LtUInt32; | |
| lowOp = LeUInt32; | |
| break; | |
| case GtUInt64: | |
| highOp = GtUInt32; | |
| lowOp = GtUInt32; | |
| break; | |
| case GeUInt64: | |
| highOp = GtUInt32; | |
| lowOp = GeUInt32; | |
| break; | |
| default: | |
| abort(); | |
| } | |
| Binary* compHigh = | |
| builder->makeBinary(highOp, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32)); | |
| Binary* eqHigh = | |
| builder->makeBinary(EqInt32, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32)); | |
| Binary* compLow = | |
| builder->makeBinary(lowOp, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32)); | |
| return builder->blockify( | |
| result, | |
| builder->makeBinary( | |
| OrInt32, compHigh, builder->makeBinary(AndInt32, eqHigh, compLow))); | |
| } | |
| Block* lowerSComp(BinaryOp op, | |
| Block* result, | |
| TempVar&& leftLow, | |
| TempVar&& leftHigh, | |
| TempVar&& rightLow, | |
| TempVar&& rightHigh) { | |
| BinaryOp highOp1, highOp2, lowOp; | |
| switch (op) { | |
| case LtSInt64: | |
| highOp1 = LtSInt32; | |
| highOp2 = LeSInt32; | |
| lowOp = GeUInt32; | |
| break; | |
| case LeSInt64: | |
| highOp1 = LtSInt32; | |
| highOp2 = LeSInt32; | |
| lowOp = GtUInt32; | |
| break; | |
| case GtSInt64: | |
| highOp1 = GtSInt32; | |
| highOp2 = GeSInt32; | |
| lowOp = LeUInt32; | |
| break; | |
| case GeSInt64: | |
| highOp1 = GtSInt32; | |
| highOp2 = GeSInt32; | |
| lowOp = LtUInt32; | |
| break; | |
| default: | |
| abort(); | |
| } | |
| Binary* compHigh1 = | |
| builder->makeBinary(highOp1, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32)); | |
| Binary* compHigh2 = | |
| builder->makeBinary(highOp2, | |
| builder->makeLocalGet(leftHigh, Type::i32), | |
| builder->makeLocalGet(rightHigh, Type::i32)); | |
| Binary* compLow = | |
| builder->makeBinary(lowOp, | |
| builder->makeLocalGet(leftLow, Type::i32), | |
| builder->makeLocalGet(rightLow, Type::i32)); | |
| If* lowIf = builder->makeIf( | |
| compLow, builder->makeConst(int32_t(0)), builder->makeConst(int32_t(1))); | |
| If* highIf2 = | |
| builder->makeIf(compHigh2, lowIf, builder->makeConst(int32_t(0))); | |
| If* highIf1 = | |
| builder->makeIf(compHigh1, builder->makeConst(int32_t(1)), highIf2); | |
| return builder->blockify(result, highIf1); | |
| } | |
| bool binaryNeedsLowering(BinaryOp op) { | |
| switch (op) { | |
| case AddInt64: | |
| case SubInt64: | |
| case MulInt64: | |
| case DivSInt64: | |
| case DivUInt64: | |
| case RemSInt64: | |
| case RemUInt64: | |
| case AndInt64: | |
| case OrInt64: | |
| case XorInt64: | |
| case ShlInt64: | |
| case ShrUInt64: | |
| case ShrSInt64: | |
| case RotLInt64: | |
| case RotRInt64: | |
| case EqInt64: | |
| case NeInt64: | |
| case LtSInt64: | |
| case LtUInt64: | |
| case LeSInt64: | |
| case LeUInt64: | |
| case GtSInt64: | |
| case GtUInt64: | |
| case GeSInt64: | |
| case GeUInt64: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| void visitBinary(Binary* curr) { | |
| if (handleUnreachable(curr)) { | |
| return; | |
| } | |
| if (!binaryNeedsLowering(curr->op)) { | |
| return; | |
| } | |
| // left and right reachable, lower normally | |
| TempVar leftLow = getTemp(); | |
| TempVar leftHigh = fetchOutParam(curr->left); | |
| TempVar rightLow = getTemp(); | |
| TempVar rightHigh = fetchOutParam(curr->right); | |
| LocalSet* setRight = builder->makeLocalSet(rightLow, curr->right); | |
| LocalSet* setLeft = builder->makeLocalSet(leftLow, curr->left); | |
| Block* result = builder->blockify(setLeft, setRight); | |
| switch (curr->op) { | |
| case AddInt64: { | |
| replaceCurrent(lowerAdd(result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| } | |
| case SubInt64: { | |
| replaceCurrent(lowerSub(result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| } | |
| case MulInt64: | |
| case DivSInt64: | |
| case DivUInt64: | |
| case RemSInt64: | |
| case RemUInt64: | |
| case RotLInt64: | |
| case RotRInt64: | |
| WASM_UNREACHABLE("should have been removed by now"); | |
| case AndInt64: | |
| case OrInt64: | |
| case XorInt64: { | |
| replaceCurrent(lowerBitwise(curr->op, | |
| result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| } | |
| case ShlInt64: | |
| case ShrSInt64: | |
| case ShrUInt64: { | |
| replaceCurrent(lowerShift(curr->op, | |
| result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| } | |
| case EqInt64: { | |
| replaceCurrent(lowerEq(result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| } | |
| case NeInt64: { | |
| replaceCurrent(lowerNe(result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| } | |
| case LtSInt64: | |
| case LeSInt64: | |
| case GtSInt64: | |
| case GeSInt64: | |
| replaceCurrent(lowerSComp(curr->op, | |
| result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| case LtUInt64: | |
| case LeUInt64: | |
| case GtUInt64: | |
| case GeUInt64: { | |
| replaceCurrent(lowerUComp(curr->op, | |
| result, | |
| std::move(leftLow), | |
| std::move(leftHigh), | |
| std::move(rightLow), | |
| std::move(rightHigh))); | |
| break; | |
| } | |
| default: { | |
| std::cerr << "Unhandled binary op " << curr->op << std::endl; | |
| abort(); | |
| } | |
| } | |
| } | |
| void visitWideIntAddSub(WideIntAddSub* curr) { | |
| WASM_UNREACHABLE("TODO: wide arithmetic lowering"); | |
| } | |
| void visitWideIntMul(WideIntMul* curr) { | |
| WASM_UNREACHABLE("TODO: wide arithmetic lowering"); | |
| } | |
| void visitSelect(Select* curr) { | |
| if (handleUnreachable(curr)) { | |
| return; | |
| } | |
| if (!hasOutParam(curr->ifTrue)) { | |
| assert(!hasOutParam(curr->ifFalse)); | |
| return; | |
| } | |
| assert(hasOutParam(curr->ifFalse)); | |
| TempVar highBits = getTemp(); | |
| TempVar lowBits = getTemp(); | |
| TempVar cond = getTemp(); | |
| Block* result = builder->blockify( | |
| builder->makeLocalSet(cond, curr->condition), | |
| builder->makeLocalSet( | |
| lowBits, | |
| builder->makeSelect( | |
| builder->makeLocalGet(cond, Type::i32), curr->ifTrue, curr->ifFalse)), | |
| builder->makeLocalSet( | |
| highBits, | |
| builder->makeSelect( | |
| builder->makeLocalGet(cond, Type::i32), | |
| builder->makeLocalGet(fetchOutParam(curr->ifTrue), Type::i32), | |
| builder->makeLocalGet(fetchOutParam(curr->ifFalse), Type::i32))), | |
| builder->makeLocalGet(lowBits, Type::i32)); | |
| setOutParam(result, std::move(highBits)); | |
| replaceCurrent(result); | |
| } | |
| void visitDrop(Drop* curr) { | |
| if (!hasOutParam(curr->value)) { | |
| return; | |
| } | |
| // free temp var | |
| fetchOutParam(curr->value); | |
| } | |
| void visitReturn(Return* curr) { | |
| if (!hasOutParam(curr->value)) { | |
| return; | |
| } | |
| TempVar lowBits = getTemp(); | |
| TempVar highBits = fetchOutParam(curr->value); | |
| LocalSet* setLow = builder->makeLocalSet(lowBits, curr->value); | |
| GlobalSet* setHigh = builder->makeGlobalSet( | |
| INT64_TO_32_HIGH_BITS, builder->makeLocalGet(highBits, Type::i32)); | |
| curr->value = builder->makeLocalGet(lowBits, Type::i32); | |
| Block* result = builder->blockify(setLow, setHigh, curr); | |
| replaceCurrent(result); | |
| } | |
| private: | |
| std::unique_ptr<Builder> builder; | |
| std::unordered_map<Index, Index> indexMap; | |
| std::unordered_map<int, std::vector<Index>> freeTemps; | |
| std::unordered_map<Expression*, TempVar> highBitVars; | |
| std::unordered_map<Index, Type> tempTypes; | |
| std::unordered_set<Name> originallyI64Globals; | |
| Index nextTemp; | |
| TempVar getTemp(Type ty = Type::i32) { | |
| Index ret; | |
| auto& freeList = freeTemps[ty.getBasic()]; | |
| if (freeList.size() > 0) { | |
| ret = freeList.back(); | |
| freeList.pop_back(); | |
| } else { | |
| ret = nextTemp++; | |
| tempTypes[ret] = ty; | |
| } | |
| assert(tempTypes[ret] == ty); | |
| return TempVar(ret, ty, *this); | |
| } | |
| bool hasOutParam(Expression* e) { | |
| return highBitVars.find(e) != highBitVars.end(); | |
| } | |
| void setOutParam(Expression* e, TempVar&& var) { | |
| highBitVars.emplace(e, std::move(var)); | |
| } | |
| TempVar fetchOutParam(Expression* e) { | |
| auto outParamIt = highBitVars.find(e); | |
| assert(outParamIt != highBitVars.end()); | |
| TempVar ret = std::move(outParamIt->second); | |
| highBitVars.erase(e); | |
| return ret; | |
| } | |
| // If e.g. a select is unreachable, then one arm may have an out param | |
| // but not the other. In this case dce should really have been run | |
| // before; handle it in a simple way here by replacing the node with | |
| // a block of its children. | |
| // This is valid only for nodes that execute their children | |
| // unconditionally before themselves, so it is not valid for an if, | |
| // in particular. | |
| bool handleUnreachable(Expression* curr) { | |
| if (curr->type != Type::unreachable) { | |
| return false; | |
| } | |
| std::vector<Expression*> children; | |
| bool hasUnreachable = false; | |
| for (auto* child : ChildIterator(curr)) { | |
| if (child->type.isConcrete()) { | |
| child = builder->makeDrop(child); | |
| } else if (child->type == Type::unreachable) { | |
| hasUnreachable = true; | |
| } | |
| children.push_back(child); | |
| } | |
| if (!hasUnreachable) { | |
| return false; | |
| } | |
| // This has an unreachable child, so we can replace it with | |
| // the children. | |
| auto* block = builder->makeBlock(children); | |
| assert(block->type == Type::unreachable); | |
| replaceCurrent(block); | |
| return true; | |
| } | |
| }; | |
| Pass* createI64ToI32LoweringPass() { return new I64ToI32Lowering(); } | |
| } // namespace wasm |