/* * 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. // #include #include "wasm.h" #include "pass.h" #include "emscripten-optimizer/istring.h" #include "support/name.h" #include "wasm-builder.h" namespace wasm { static Name makeHighName(Name n) { return Name( cashew::IString((std::string(n.c_str()) + "$hi").c_str(), false) ); } struct I64ToI32Lowering : public WalkerPass> { struct TempVar { TempVar(Index idx, I64ToI32Lowering& pass) : idx(idx), pass(pass), moved(false) {} TempVar(TempVar&& other) : idx(other), pass(other.pass), moved(false) { 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() { assert(std::find(pass.freeTemps.begin(), pass.freeTemps.end(), idx) == pass.freeTemps.end()); pass.freeTemps.push_back(idx); } Index idx; I64ToI32Lowering& pass; bool moved; // since C++ will still destruct moved-from values }; static Name highBitsGlobal; // false since function types need to be lowered // TODO: allow module-level transformations in parallel passes bool isFunctionParallel() override { return false; } Pass* create() override { return new I64ToI32Lowering; } void doWalkModule(Module* module) { if (!builder) builder = make_unique(*module); // add new globals for high bits for (size_t i = 0, globals = module->globals.size(); i < globals; ++i) { auto& curr = module->globals[i]; if (curr->type != i64) continue; curr->type = i32; auto* high = new Global(*curr); high->name = makeHighName(curr->name); module->addGlobal(high); } PostWalker::doWalkModule(module); } void visitFunctionType(FunctionType* curr) { std::vector params; for (auto t : curr->params) { if (t == i64) { params.push_back(i32); params.push_back(i32); } else { params.push_back(t); } } std::swap(params, curr->params); if (curr->result == i64) { curr->result = i32; } } void doWalkFunction(Function* func) { // create builder here if this is first entry to module for this object if (!builder) builder = make_unique(*getModule()); indexMap.clear(); highBitVars.clear(); labelHighBitVars.clear(); freeTemps.clear(); Function oldFunc(*func); func->params.clear(); func->vars.clear(); func->localNames.clear(); func->localIndices.clear(); Index newIdx = 0; for (Index i = 0; i < oldFunc.getNumLocals(); ++i) { assert(oldFunc.hasLocalName(i)); Name lowName = oldFunc.getLocalName(i); Name highName = makeHighName(lowName); WasmType paramType = oldFunc.getLocalType(i); auto builderFunc = (i < oldFunc.getVarIndexBase()) ? Builder::addParam : static_cast(Builder::addVar); if (paramType == i64) { builderFunc(func, lowName, i32); builderFunc(func, highName, i32); indexMap[i] = newIdx; newIdx += 2; } else { builderFunc(func, lowName, paramType); indexMap[i] = newIdx++; } } nextTemp = func->getNumLocals(); PostWalker::doWalkFunction(func); } void visitFunction(Function* func) { if (func->result == i64) { func->result = 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(); SetLocal* setLow = builder->makeSetLocal( lowBits, func->body ); SetGlobal* setHigh = builder->makeSetGlobal( highBitsGlobal, builder->makeGetLocal(highBits, i32) ); GetLocal* getLow = builder->makeGetLocal(lowBits, i32); func->body = builder->blockify(setLow, setHigh, getLow); } } assert(freeTemps.size() == nextTemp - func->getNumLocals()); int idx = 0; for (size_t i = func->getNumLocals(); i < nextTemp; i++) { Name tmpName("i64toi32_i32$" + std::to_string(idx++)); builder->addVar(func, tmpName, i32); } } void visitBlock(Block* curr) { if (curr->list.size() == 0) return; if (curr->type == i64) curr->type = i32; auto highBitsIt = labelHighBitVars.find(curr->name); if (!hasOutParam(curr->list.back())) { if (highBitsIt != labelHighBitVars.end()) { setOutParam(curr, std::move(highBitsIt->second)); } return; } TempVar lastHighBits = fetchOutParam(curr->list.back()); if (highBitsIt == labelHighBitVars.end() || highBitsIt->second == lastHighBits) { setOutParam(curr, std::move(lastHighBits)); if (highBitsIt != labelHighBitVars.end()) { labelHighBitVars.erase(highBitsIt); } return; } TempVar highBits = std::move(highBitsIt->second); TempVar tmp = getTemp(); labelHighBitVars.erase(highBitsIt); SetLocal* setLow = builder->makeSetLocal(tmp, curr->list.back()); SetLocal* setHigh = builder->makeSetLocal( highBits, builder->makeGetLocal(lastHighBits, i32) ); GetLocal* getLow = builder->makeGetLocal(tmp, i32); curr->list.back() = builder->blockify(setLow, setHigh, getLow); setOutParam(curr, std::move(highBits)); } // If and Select have identical code template void visitBranching(T* curr) { if (!hasOutParam(curr->ifTrue)) return; assert(curr->ifFalse != nullptr && "Nullable ifFalse found"); TempVar highBits = fetchOutParam(curr->ifTrue); TempVar falseBits = fetchOutParam(curr->ifFalse); TempVar tmp = getTemp(); curr->type = i32; curr->ifFalse = builder->blockify( builder->makeSetLocal(tmp, curr->ifFalse), builder->makeSetLocal( highBits, builder->makeGetLocal(falseBits, i32) ), builder->makeGetLocal(tmp, i32) ); setOutParam(curr, std::move(highBits)); } void visitIf(If* curr) { visitBranching(curr); } void visitLoop(Loop* curr) { assert(labelHighBitVars.find(curr->name) == labelHighBitVars.end()); if (curr->type != i64) return; curr->type = i32; setOutParam(curr, fetchOutParam(curr->body)); } void visitBreak(Break* curr) { if (!hasOutParam(curr->value)) return; assert(curr->value != nullptr); TempVar valHighBits = fetchOutParam(curr->value); auto blockHighBitsIt = labelHighBitVars.find(curr->name); if (blockHighBitsIt == labelHighBitVars.end()) { labelHighBitVars.emplace(curr->name, std::move(valHighBits)); curr->type = i32; return; } TempVar blockHighBits = std::move(blockHighBitsIt->second); TempVar tmp = getTemp(); SetLocal* setLow = builder->makeSetLocal(tmp, curr->value); SetLocal* setHigh = builder->makeSetLocal( blockHighBits, builder->makeGetLocal(valHighBits, i32) ); curr->value = builder->makeGetLocal(tmp, i32); curr->type = i32; replaceCurrent(builder->blockify(setLow, setHigh, curr)); } void visitSwitch(Switch* curr) { if (!hasOutParam(curr->value)) return; TempVar outParam = fetchOutParam(curr->value); TempVar tmp = getTemp(); Expression* result = curr; std::vector targets; size_t blockID = 0; auto processTarget = [&](Name target) -> Name { auto labelIt = labelHighBitVars.find(target); if (labelIt == labelHighBitVars.end()) { labelHighBitVars.emplace(target, getTemp()); labelIt = labelHighBitVars.find(target); } Name newLabel("$i64toi32_" + std::string(target.c_str()) + "_" + std::to_string(blockID++)); Block* trampoline = builder->makeBlock(newLabel, result); trampoline->type = i32; result = builder->blockify( builder->makeSetLocal(tmp, trampoline), builder->makeSetLocal( labelIt->second, builder->makeGetLocal(outParam, i32) ), builder->makeBreak(target, builder->makeGetLocal(tmp, i32)) ); return newLabel; }; for (auto target : curr->targets) { targets.push_back(processTarget(target)); } curr->targets.set(targets); curr->default_ = processTarget(curr->default_); replaceCurrent(result); } template using BuilderFunc = std::function&, WasmType)>; template void visitGenericCall(T* curr, BuilderFunc callBuilder) { std::vector args; for (auto* e : curr->operands) { args.push_back(e); if (hasOutParam(e)) { TempVar argHighBits = fetchOutParam(e); args.push_back(builder->makeGetLocal(argHighBits, i32)); } } if (curr->type != i64) { replaceCurrent(callBuilder(args, curr->type)); return; } TempVar lowBits = getTemp(); TempVar highBits = getTemp(); SetLocal* doCall = builder->makeSetLocal( lowBits, callBuilder(args, i32) ); SetLocal* setHigh = builder->makeSetLocal( highBits, builder->makeGetGlobal(highBitsGlobal, i32) ); GetLocal* getLow = builder->makeGetLocal(lowBits, i32); Block* result = builder->blockify(doCall, setHigh, getLow); setOutParam(result, std::move(highBits)); replaceCurrent(result); } void visitCall(Call* curr) { visitGenericCall( curr, [&](std::vector& args, WasmType ty) { return builder->makeCall(curr->target, args, ty); } ); } void visitCallImport(CallImport* curr) { // imports cannot contain i64s return; } void visitCallIndirect(CallIndirect* curr) { visitGenericCall( curr, [&](std::vector& args, WasmType ty) { return builder->makeCallIndirect( curr->fullType, curr->target, args, ty ); } ); } void visitGetLocal(GetLocal* curr) { if (curr->type != i64) return; curr->index = indexMap[curr->index]; curr->type = i32; TempVar highBits = getTemp(); SetLocal *setHighBits = builder->makeSetLocal( highBits, builder->makeGetLocal( curr->index + 1, i32 ) ); Block* result = builder->blockify(setHighBits, curr); replaceCurrent(result); setOutParam(result, std::move(highBits)); } void lowerTee(SetLocal* curr) { TempVar highBits = fetchOutParam(curr->value); TempVar tmp = getTemp(); curr->index = indexMap[curr->index]; curr->type = i32; SetLocal* setLow = builder->makeSetLocal(tmp, curr); SetLocal* setHigh = builder->makeSetLocal( curr->index + 1, builder->makeGetLocal(highBits, i32) ); GetLocal* getLow = builder->makeGetLocal(tmp, i32); Block* result = builder->blockify(setLow, setHigh, getLow); replaceCurrent(result); setOutParam(result, std::move(highBits)); } void visitSetLocal(SetLocal* curr) { if (!hasOutParam(curr->value)) return; if (curr->isTee()) { lowerTee(curr); return; } TempVar highBits = fetchOutParam(curr->value); curr->index = indexMap[curr->index]; SetLocal* setHigh = builder->makeSetLocal( curr->index + 1, builder->makeGetLocal(highBits, i32) ); Block* result = builder->blockify(curr, setHigh); replaceCurrent(result); } void visitGetGlobal(GetGlobal* curr) { assert(false && "GetGlobal not implemented"); } void visitSetGlobal(SetGlobal* curr) { assert(false && "SetGlobal not implemented"); } void visitLoad(Load* curr) { if (curr->type != i64) return; assert(!curr->isAtomic && "atomic load not implemented"); TempVar highBits = getTemp(); TempVar ptrTemp = getTemp(); SetLocal* setPtr = builder->makeSetLocal(ptrTemp, curr->ptr); SetLocal* loadHigh; if (curr->bytes == 8) { loadHigh = builder->makeSetLocal( highBits, builder->makeLoad( 4, curr->signed_, curr->offset + 4, 1, builder->makeGetLocal(ptrTemp, i32), i32 ) ); } else { loadHigh = builder->makeSetLocal( highBits, builder->makeConst(Literal(int32_t(0))) ); } curr->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->makeGetLocal(ptrTemp, i32); Block* result = builder->blockify(setPtr, loadHigh, curr); 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 = i32; if (bytes == 8) { TempVar ptrTemp = getTemp(); SetLocal* setPtr = builder->makeSetLocal(ptrTemp, curr->ptr); curr->ptr = builder->makeGetLocal(ptrTemp, i32); Store* storeHigh = builder->makeStore( 4, curr->offset + 4, 1, builder->makeGetLocal(ptrTemp, i32), builder->makeGetLocal(highBits, i32), i32 ); replaceCurrent(builder->blockify(setPtr, curr, storeHigh)); } } void visitAtomicRMW(AtomicRMW* curr) { assert(false && "AtomicRMW not implemented"); } void visitAtomicCmpxchg(AtomicCmpxchg* curr) { assert(false && "AtomicCmpxchg not implemented"); } void visitConst(Const* curr) { if (curr->type != i64) return; TempVar highBits = getTemp(); Const* lowVal = builder->makeConst( Literal(int32_t(curr->value.geti64() & 0xffffffff)) ); SetLocal* setHigh = builder->makeSetLocal( highBits, builder->makeConst( Literal(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); replaceCurrent( builder->makeBinary( AndInt32, builder->makeUnary(EqZInt32, builder->makeGetLocal(highBits, i32)), builder->makeUnary(EqZInt32, curr->value) ) ); } void lowerExtendUInt32(Unary* curr) { TempVar highBits = getTemp(); Block* result = builder->blockify( builder->makeSetLocal(highBits, builder->makeConst(Literal(int32_t(0)))), curr->value ); setOutParam(result, std::move(highBits)); replaceCurrent(result); } void lowerWrapInt64(Unary* curr) { // free the temp var fetchOutParam(curr->value); replaceCurrent(curr->value); } bool unaryNeedsLowering(UnaryOp op) { switch (op) { case ClzInt64: case CtzInt64: case PopcntInt64: case EqZInt64: case ExtendSInt32: case ExtendUInt32: case WrapInt64: case TruncSFloat32ToInt64: case TruncUFloat32ToInt64: case TruncSFloat64ToInt64: case TruncUFloat64ToInt64: case ReinterpretFloat64: case ConvertSInt64ToFloat32: case ConvertSInt64ToFloat64: case ConvertUInt64ToFloat32: case ConvertUInt64ToFloat64: case ReinterpretInt64: return true; default: return false; } } void visitUnary(Unary* curr) { if (!unaryNeedsLowering(curr->op)) return; if (curr->type == unreachable || curr->value->type == unreachable) { assert(!hasOutParam(curr->value)); replaceCurrent(curr->value); return; } assert(hasOutParam(curr->value) || curr->type == i64); switch (curr->op) { case ClzInt64: case CtzInt64: case PopcntInt64: goto err; case EqZInt64: lowerEqZInt64(curr); break; case ExtendSInt32: goto err; case ExtendUInt32: lowerExtendUInt32(curr); break; case WrapInt64: lowerWrapInt64(curr); break; case TruncSFloat32ToInt64: case TruncUFloat32ToInt64: case TruncSFloat64ToInt64: case TruncUFloat64ToInt64: case ReinterpretFloat64: case ConvertSInt64ToFloat32: case ConvertSInt64ToFloat64: case ConvertUInt64ToFloat32: case ConvertUInt64ToFloat64: case ReinterpretInt64: err: default: std::cerr << "Unhandled unary operator: " << curr->op << std::endl; abort(); } } Block* lowerAdd(Block* result, TempVar&& leftLow, TempVar&& leftHigh, TempVar&& rightLow, TempVar&& rightHigh) { SetLocal* addLow = builder->makeSetLocal( leftHigh, builder->makeBinary( AddInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightLow, i32) ) ); SetLocal* addHigh = builder->makeSetLocal( rightHigh, builder->makeBinary( AddInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(rightHigh, i32) ) ); SetLocal* carryBit = builder->makeSetLocal( rightHigh, builder->makeBinary( AddInt32, builder->makeGetLocal(rightHigh, i32), builder->makeConst(Literal(int32_t(1))) ) ); If* checkOverflow = builder->makeIf( builder->makeBinary( LtUInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightLow, i32) ), carryBit ); GetLocal* getLow = builder->makeGetLocal(leftHigh, i32); result = builder->blockify(result, addLow, addHigh, checkOverflow, getLow); setOutParam(result, std::move(rightHigh)); return result; } Block* lowerMul(Block* result, TempVar&& leftLow, TempVar&& leftHigh, TempVar&& rightLow, TempVar&& rightHigh) { // high bits = ll*rh + lh*rl + ll1*rl1 + (ll0*rl1)>>16 + (ll1*rl0)>>16 // low bits = ll*rl TempVar leftLow0 = getTemp(); TempVar leftLow1 = getTemp(); TempVar rightLow0 = getTemp(); TempVar rightLow1 = getTemp(); SetLocal* setLL0 = builder->makeSetLocal( leftLow0, builder->makeBinary( AndInt32, builder->makeGetLocal(leftLow, i32), builder->makeConst(Literal(int32_t(0xffff))) ) ); SetLocal* setLL1 = builder->makeSetLocal( leftLow1, builder->makeBinary( ShrUInt32, builder->makeGetLocal(leftLow, i32), builder->makeConst(Literal(int32_t(16))) ) ); SetLocal* setRL0 = builder->makeSetLocal( rightLow0, builder->makeBinary( AndInt32, builder->makeGetLocal(rightLow, i32), builder->makeConst(Literal(int32_t(0xffff))) ) ); SetLocal* setRL1 = builder->makeSetLocal( rightLow1, builder->makeBinary( ShrUInt32, builder->makeGetLocal(rightLow, i32), builder->makeConst(Literal(int32_t(16))) ) ); SetLocal* setLLRH = builder->makeSetLocal( rightHigh, builder->makeBinary( MulInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightHigh, i32) ) ); auto addToHighBits = [&](Expression* expr) -> SetLocal* { return builder->makeSetLocal( rightHigh, builder->makeBinary( AddInt32, builder->makeGetLocal(rightHigh, i32), expr ) ); }; SetLocal* addLHRL = addToHighBits( builder->makeBinary( MulInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(rightLow, i32) ) ); SetLocal* addLL1RL1 = addToHighBits( builder->makeBinary( MulInt32, builder->makeGetLocal(leftLow1, i32), builder->makeGetLocal(rightLow1, i32) ) ); SetLocal* addLL0RL1 = addToHighBits( builder->makeBinary( ShrUInt32, builder->makeBinary( MulInt32, builder->makeGetLocal(leftLow0, i32), builder->makeGetLocal(rightLow1, i32) ), builder->makeConst(Literal(int32_t(16))) ) ); SetLocal* addLL1RL0 = addToHighBits( builder->makeBinary( ShrUInt32, builder->makeBinary( MulInt32, builder->makeGetLocal(leftLow1, i32), builder->makeGetLocal(rightLow0, i32) ), builder->makeConst(Literal(int32_t(16))) ) ); Binary* getLow = builder->makeBinary( MulInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightLow, i32) ); result = builder->blockify( result, setLL0, setLL1, setRL0, setRL1, setLLRH, addLHRL, addLL1RL1, addLL0RL1, addLL1RL0, getLow ); setOutParam(result, std::move(rightHigh)); 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->makeSetLocal( rightHigh, builder->makeBinary( op32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(rightHigh, i32) ) ), builder->makeBinary( op32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightLow, i32) ) ); setOutParam(result, std::move(rightHigh)); return result; } Block* makeLargeShl(Index highBits, Index leftLow, Index shift) { return builder->blockify( builder->makeSetLocal( highBits, builder->makeBinary( ShlInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(shift, i32) ) ), builder->makeConst(Literal(int32_t(0))) ); } Block* makeLargeShrU(Index highBits, Index leftHigh, Index shift) { return builder->blockify( builder->makeSetLocal(highBits, builder->makeConst(Literal(int32_t(0)))), builder->makeBinary( ShrUInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(shift, 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->makeGetLocal(leftLow, i32), widthLessShift ) ); Binary* shiftHigh = builder->makeBinary( ShlInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(shift, i32) ); return builder->blockify( builder->makeSetLocal( highBits, builder->makeBinary(OrInt32, shiftedInBits, shiftHigh) ), builder->makeBinary( ShlInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(shift, i32) ) ); } Block* makeSmallShrU(Index highBits, Index leftLow, Index leftHigh, Index shift, Binary* shiftMask, Binary* widthLessShift) { Binary* shiftedInBits = builder->makeBinary( ShlInt32, builder->makeBinary( AndInt32, shiftMask, builder->makeGetLocal(leftHigh, i32) ), widthLessShift ); Binary* shiftLow = builder->makeBinary( ShrUInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(shift, i32) ); return builder->blockify( builder->makeSetLocal( highBits, builder->makeBinary( ShrUInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(shift, i32) ) ), builder->makeBinary(OrInt32, shiftedInBits, shiftLow) ); } Block* lowerShU(BinaryOp op, Block* result, TempVar&& leftLow, TempVar&& leftHigh, TempVar&& rightLow, TempVar&& rightHigh) { assert(op == ShlInt64 || op == ShrUInt64); // 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(); SetLocal* setShift = builder->makeSetLocal( shift, builder->makeBinary( AndInt32, builder->makeGetLocal(rightLow, i32), builder->makeConst(Literal(int32_t(32 - 1))) ) ); Binary* isLargeShift = builder->makeBinary( LeUInt32, builder->makeConst(Literal(int32_t(32))), builder->makeBinary( AndInt32, builder->makeGetLocal(rightLow, i32), builder->makeConst(Literal(int32_t(64 - 1))) ) ); Block* largeShiftBlock; switch (op) { case ShlInt64: largeShiftBlock = makeLargeShl(rightHigh, leftLow, shift); break; case ShrUInt64: largeShiftBlock = makeLargeShrU(rightHigh, leftHigh, shift); break; default: abort(); } Binary* shiftMask = builder->makeBinary( SubInt32, builder->makeBinary( ShlInt32, builder->makeConst(Literal(int32_t(1))), builder->makeGetLocal(shift, i32) ), builder->makeConst(Literal(int32_t(1))) ); Binary* widthLessShift = builder->makeBinary( SubInt32, builder->makeConst(Literal(int32_t(32))), builder->makeGetLocal(shift, i32) ); Block* smallShiftBlock; switch(op) { case ShlInt64: { smallShiftBlock = makeSmallShl(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->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightLow, i32) ), builder->makeBinary( EqInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(rightHigh, i32) ) ) ); } Block* lowerNe(Block* result, TempVar&& leftLow, TempVar&& leftHigh, TempVar&& rightLow, TempVar&& rightHigh) { return builder->blockify( result, builder->makeBinary( OrInt32, builder->makeBinary( NeInt32, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightLow, i32) ), builder->makeBinary( NeInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(rightHigh, 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->makeGetLocal(leftHigh, i32), builder->makeGetLocal(rightHigh, i32) ); Binary* eqHigh = builder->makeBinary( EqInt32, builder->makeGetLocal(leftHigh, i32), builder->makeGetLocal(rightHigh, i32) ); Binary* compLow = builder->makeBinary( lowOp, builder->makeGetLocal(leftLow, i32), builder->makeGetLocal(rightLow, i32) ); return builder->blockify( result, builder->makeBinary( OrInt32, compHigh, builder->makeBinary(AndInt32, eqHigh, compLow) ) ); } 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 (!binaryNeedsLowering(curr->op)) return; if (!hasOutParam(curr->left)) { // left unreachable, replace self with left replaceCurrent(curr->left); if (hasOutParam(curr->right)) { // free temp var fetchOutParam(curr->right); } return; } if (!hasOutParam(curr->right)) { // right unreachable, replace self with left then right replaceCurrent( builder->blockify(builder->makeDrop(curr->left), curr->right) ); // free temp var fetchOutParam(curr->left); return; } // left and right reachable, lower normally TempVar leftLow = getTemp(); TempVar leftHigh = fetchOutParam(curr->left); TempVar rightLow = getTemp(); TempVar rightHigh = fetchOutParam(curr->right); SetLocal* setRight = builder->makeSetLocal(rightLow, curr->right); SetLocal* setLeft = builder->makeSetLocal(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: goto err; case MulInt64: { replaceCurrent( lowerMul(result, std::move(leftLow), std::move(leftHigh), std::move(rightLow), std::move(rightHigh)) ); break; } case DivSInt64: case DivUInt64: case RemSInt64: case RemUInt64: goto err; 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 ShrUInt64: { replaceCurrent( lowerShU(curr->op, result, std::move(leftLow), std::move(leftHigh), std::move(rightLow), std::move(rightHigh)) ); break; } case ShrSInt64: case RotLInt64: case RotRInt64: goto err; 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: goto err; 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; } err: default: { std::cerr << "Unhandled binary op " << curr->op << std::endl; abort(); } } } void visitSelect(Select* curr) { visitBranching