diff --git a/evmcc/test/stack/swap.evm b/evmcc/test/stack/swap.evm index 27914aa55..d17f0ee09 100644 --- a/evmcc/test/stack/swap.evm +++ b/evmcc/test/stack/swap.evm @@ -1 +1 @@ -600160026001600a590090600160115900016000546001601ff2 +600560026001600c59505000906001601559505000036000546001601ff2 diff --git a/evmcc/test/stack/swap.lll b/evmcc/test/stack/swap.lll index e9ac214bd..90dee585d 100644 --- a/evmcc/test/stack/swap.lll +++ b/evmcc/test/stack/swap.lll @@ -1,20 +1,26 @@ (asm -1 ;; 0 +5 ;; 0 2 ;; 2 1 ;; 4 -10 ;; 6 +12 ;; 6 JUMPI ;; 8 -STOP ;; 9 + +POP ;; 9 +POP ;; 10 +STOP ;; 11 ;; stack = 2,1 -SWAP1 ;; 10 -1 ;; 11 -17 ;; 13 -JUMPI ;; 15 -STOP ;; 16 +SWAP1 ;; 12 +1 ;; 13 +21 ;; 15 +JUMPI ;; 17 + +POP ;; 18 +POP ;; 19 +STOP ;; 20 ;; stack = 1,2 -ADD ;; 17 +SUB ;; 21 0 MSTORE 1 diff --git a/evmcc/test/stack/swapswap.evm b/evmcc/test/stack/swapswap.evm index f0081f238..fb4f1bf75 100644 --- a/evmcc/test/stack/swapswap.evm +++ b/evmcc/test/stack/swapswap.evm @@ -1 +1 @@ -600160026001600a59009090600160125900016000546001601ff2 +600260056001600c5950500090906001601659505000036000546001601ff2 diff --git a/evmcc/test/stack/swapswap.lll b/evmcc/test/stack/swapswap.lll index 67fe75941..1fedf726e 100644 --- a/evmcc/test/stack/swapswap.lll +++ b/evmcc/test/stack/swapswap.lll @@ -1,21 +1,27 @@ (asm -1 ;; 0 -2 ;; 2 +2 ;; 0 +5 ;; 2 1 ;; 4 -10 ;; 6 +12 ;; 6 JUMPI ;; 8 -STOP ;; 9 + +POP ;; 9 +POP ;; 10 +STOP ;; 11 ;; stack = 2,1 -SWAP1 ;; 10 -SWAP1 ;; 11 -1 ;; 12 -18 ;; 14 -JUMPI ;; 16 -STOP ;; 17 +SWAP1 ;; 12 +SWAP1 ;; 13 +1 ;; 14 +22 ;; 16 +JUMPI ;; 18 + +POP ;; 19 +POP ;; 20 +STOP ;; 21 ;; stack = 2,1 -ADD ;; 18 +SUB ;; 22 0 MSTORE 1 diff --git a/libevmjit/BasicBlock.cpp b/libevmjit/BasicBlock.cpp index cc0efc11a..1756971be 100644 --- a/libevmjit/BasicBlock.cpp +++ b/libevmjit/BasicBlock.cpp @@ -5,9 +5,11 @@ #include +#include #include #include #include +#include #include "Type.h" @@ -24,17 +26,18 @@ BasicBlock::BasicBlock(ProgramCounter _beginInstIdx, ProgramCounter _endInstIdx, m_beginInstIdx(_beginInstIdx), m_endInstIdx(_endInstIdx), m_llvmBB(llvm::BasicBlock::Create(_mainFunc->getContext(), {NamePrefix, std::to_string(_beginInstIdx)}, _mainFunc)), - m_stack(_builder) + m_stack(_builder, m_llvmBB) {} BasicBlock::BasicBlock(std::string _name, llvm::Function* _mainFunc, llvm::IRBuilder<>& _builder) : m_beginInstIdx(0), m_endInstIdx(0), m_llvmBB(llvm::BasicBlock::Create(_mainFunc->getContext(), _name, _mainFunc)), - m_stack(_builder) + m_stack(_builder, m_llvmBB) {} -BasicBlock::LocalStack::LocalStack(llvm::IRBuilder<>& _builder) : +BasicBlock::LocalStack::LocalStack(llvm::IRBuilder<>& _builder, llvm::BasicBlock* _llvmBB) : + m_llvmBB(_llvmBB), m_builder(_builder), m_initialStack(), m_currentStack(), @@ -82,7 +85,7 @@ void BasicBlock::LocalStack::swap(size_t _index) void BasicBlock::LocalStack::synchronize(Stack& _evmStack) { - auto blockTerminator = m_builder.GetInsertBlock()->getTerminator(); + auto blockTerminator = m_llvmBB->getTerminator(); assert(blockTerminator != nullptr); m_builder.SetInsertPoint(blockTerminator); @@ -182,44 +185,190 @@ void BasicBlock::LocalStack::set(size_t _index, llvm::Value* _word) } + +void BasicBlock::linkLocalStacks(std::vector basicBlocks, llvm::IRBuilder<>& _builder) +{ + struct BBInfo + { + BasicBlock& bblock; + std::vector predecessors; + size_t inputItems; + size_t outputItems; + std::vector phisToRewrite; + + BBInfo(BasicBlock& _bblock) : + bblock(_bblock), + predecessors(), + inputItems(0), + outputItems(0) + { + auto& initialStack = bblock.localStack().m_initialStack; + for (auto it = initialStack.begin(); + it != initialStack.end() && *it != nullptr; + ++it, ++inputItems); + + //if (bblock.localStack().m_tosOffset > 0) + // outputItems = bblock.localStack().m_tosOffset; + auto& exitStack = bblock.localStack().m_currentStack; + for (auto it = exitStack.rbegin(); + it != exitStack.rend() && *it != nullptr; + ++it, ++outputItems); + } + }; + + std::map cfg; + + // Create nodes in cfg + for (auto bb : basicBlocks) + cfg.emplace(bb->llvm(), *bb); + + // Create edges in cfg: for each bb info fill the list + // of predecessor infos. + for (auto& pair : cfg) + { + auto bb = pair.first; + auto& info = pair.second; + + for (auto predIt = llvm::pred_begin(bb); predIt != llvm::pred_end(bb); ++predIt) + { + auto predInfoEntry = cfg.find(*predIt); + if (predInfoEntry != cfg.end()) + info.predecessors.push_back(&predInfoEntry->second); + } + } + + // Iteratively compute inputs and outputs of each block, until reaching fixpoint. + bool valuesChanged = true; + while (valuesChanged) + { + if (getenv("EVMCC_DEBUG_BLOCKS")) + { + for (auto& pair: cfg) + std::cerr << pair.second.bblock.llvm()->getName().str() + << ": in " << pair.second.inputItems + << ", out " << pair.second.outputItems + << "\n"; + } + + valuesChanged = false; + for (auto& pair : cfg) + { + auto& info = pair.second; + + if (info.predecessors.empty()) + info.inputItems = 0; // no consequences for other blocks, so leave valuesChanged false + + for (auto predInfo : info.predecessors) + { + if (predInfo->outputItems < info.inputItems) + { + info.inputItems = predInfo->outputItems; + valuesChanged = true; + } + else if (predInfo->outputItems > info.inputItems) + { + predInfo->outputItems = info.inputItems; + valuesChanged = true; + } + } + } + } + + // Propagate values between blocks. + for (auto& entry : cfg) + { + auto& info = entry.second; + auto& bblock = info.bblock; + + llvm::BasicBlock::iterator fstNonPhi(bblock.llvm()->getFirstNonPHI()); + auto phiIter = bblock.localStack().m_initialStack.begin(); + for (size_t index = 0; index < info.inputItems; ++index, ++phiIter) + { + assert(llvm::isa(*phiIter)); + auto phi = llvm::cast(*phiIter); + + for (auto predIt : info.predecessors) + { + auto& predExitStack = predIt->bblock.localStack().m_currentStack; + auto value = *(predExitStack.end() - 1 - index); + phi->addIncoming(value, predIt->bblock.llvm()); + } + + // Move phi to the front + if (llvm::BasicBlock::iterator(phi) != bblock.llvm()->begin()) + { + phi->removeFromParent(); + _builder.SetInsertPoint(bblock.llvm(), bblock.llvm()->begin()); + _builder.Insert(phi); + } + } + + // The items pulled directly from predecessors block must be removed + // from the list of items that has to be popped from the initial stack. + auto& initialStack = bblock.localStack().m_initialStack; + initialStack.erase(initialStack.begin(), initialStack.begin() + info.inputItems); + // Initial stack shrinks, so the size difference grows: + bblock.localStack().m_tosOffset += info.inputItems; + } + + // We must account for the items that were pushed directly to successor + // blocks and thus should not be on the list of items to be pushed onto + // to EVM stack + for (auto& entry : cfg) + { + auto& info = entry.second; + auto& bblock = info.bblock; + + auto& exitStack = bblock.localStack().m_currentStack; + exitStack.erase(exitStack.end() - info.outputItems, exitStack.end()); + bblock.localStack().m_tosOffset -= info.outputItems; + } +} + void BasicBlock::dump() { - std::cerr << "Initial stack:\n"; + dump(std::cerr, false); +} + +void BasicBlock::dump(std::ostream& _out, bool _dotOutput) +{ + llvm::raw_os_ostream out(_out); + + out << (_dotOutput ? "" : "Initial stack:\n"); for (auto val : m_stack.m_initialStack) { if (val == nullptr) - std::cerr << " ?\n"; + out << " ?"; else if (llvm::isa(val)) - val->dump(); + out << *val; else - { - std::cerr << " "; - val->dump(); - } + out << " " << *val; + + out << (_dotOutput ? "\\l" : "\n"); } - std::cerr << " ...\n"; - std::cerr << "Instructions:\n"; + out << (_dotOutput ? "| " : "Instructions:\n"); for (auto ins = m_llvmBB->begin(); ins != m_llvmBB->end(); ++ins) - ins->dump(); + out << *ins << (_dotOutput ? "\\l" : "\n"); - std::cerr << "Current stack (offset = " - << m_stack.m_tosOffset << "):\n"; + if (! _dotOutput) + out << "Current stack (offset = " << m_stack.m_tosOffset << "):\n"; + else + out << "|"; for (auto val = m_stack.m_currentStack.rbegin(); val != m_stack.m_currentStack.rend(); ++val) { if (*val == nullptr) - std::cerr << " ?\n"; + out << " ?"; else if (llvm::isa(*val)) - (*val)->dump(); + out << **val; else - { - std::cerr << " "; - (*val)->dump(); - } - + out << " " << **val; + out << (_dotOutput ? "\\l" : "\n"); } - std::cerr << " ...\n----------------------------------------\n"; + + if (! _dotOutput) + out << " ...\n----------------------------------------\n"; } diff --git a/libevmjit/BasicBlock.h b/libevmjit/BasicBlock.h index 2723cf919..65044eb2a 100644 --- a/libevmjit/BasicBlock.h +++ b/libevmjit/BasicBlock.h @@ -40,7 +40,7 @@ public: private: - LocalStack(llvm::IRBuilder<>& _builder); + LocalStack(llvm::IRBuilder<>& _builder, llvm::BasicBlock* _llvmBB); LocalStack(LocalStack const&) = delete; void operator=(LocalStack const&) = delete; friend BasicBlock; @@ -55,6 +55,8 @@ public: private: + llvm::BasicBlock* m_llvmBB; + llvm::IRBuilder<>& m_builder; /** @@ -99,9 +101,14 @@ public: LocalStack& localStack() { return m_stack; } + /// Optimization: propagates values between local stacks in basic blocks + /// to avoid excessive pushing/popping on the EVM stack. + static void linkLocalStacks(std::vector _basicBlocks, llvm::IRBuilder<>& _builder); + /// Prints local stack and block instructions to stderr. /// Useful for calling in a debugger session. void dump(); + void dump(std::ostream& os, bool _dotOutput = false); private: ProgramCounter const m_beginInstIdx; diff --git a/libevmjit/Compiler.cpp b/libevmjit/Compiler.cpp index 14952163f..339f73959 100644 --- a/libevmjit/Compiler.cpp +++ b/libevmjit/Compiler.cpp @@ -1,11 +1,13 @@ #include "Compiler.h" +#include + #include -#include -#include #include +#include +#include #include #include @@ -190,9 +192,6 @@ std::unique_ptr Compiler::compile(bytesConstRef bytecode) ++iterCopy; auto nextBasicBlock = (iterCopy != basicBlocks.end()) ? iterCopy->second.llvm() : nullptr; compileBasicBlock(basicBlock, bytecode, memory, ext, gasMeter, nextBasicBlock); - if (getenv("EVMCC_DEBUG_BLOCKS")) - basicBlock.dump(); - basicBlock.localStack().synchronize(stack); } // Code for special blocks: @@ -208,9 +207,7 @@ std::unique_ptr Compiler::compile(bytesConstRef bytecode) m_builder.SetInsertPoint(m_jumpTableBlock->llvm()); if (m_indirectJumpTargets.size() > 0) { - // auto& stack = m_jumpTableBlock->getStack(); - - auto dest = m_jumpTableBlock->localStack().pop(); //m_jumpTableBlock->localGet(0); // stack.pop(); + auto dest = m_jumpTableBlock->localStack().pop(); auto switchInstr = m_builder.CreateSwitch(dest, m_badJumpBlock->llvm(), m_indirectJumpTargets.size()); for (auto it = m_indirectJumpTargets.cbegin(); it != m_indirectJumpTargets.cend(); ++it) @@ -225,8 +222,51 @@ std::unique_ptr Compiler::compile(bytesConstRef bytecode) m_builder.CreateBr(m_badJumpBlock->llvm()); } - m_jumpTableBlock->localStack().synchronize(stack); - linkBasicBlocks(); + removeDeadBlocks(); + + if (getenv("EVMCC_DEBUG_BLOCKS")) + { + std::ofstream ofs("blocks-init.dot"); + dumpBasicBlockGraph(ofs); + ofs.close(); + std::cerr << "\n\nAfter dead block elimination \n\n"; + dump(); + } + + if (getenv("EVMCC_OPTIMIZE_STACK")) + { + std::vector blockList; + for (auto& entry : basicBlocks) + blockList.push_back(&entry.second); + + if (m_jumpTableBlock != nullptr) + blockList.push_back(m_jumpTableBlock.get()); + + BasicBlock::linkLocalStacks(blockList, m_builder); + + if (getenv("EVMCC_DEBUG_BLOCKS")) + { + std::ofstream ofs("blocks-opt.dot"); + dumpBasicBlockGraph(ofs); + ofs.close(); + std::cerr << "\n\nAfter stack optimization \n\n"; + dump(); + } + } + + for (auto& entry : basicBlocks) + entry.second.localStack().synchronize(stack); + if (m_jumpTableBlock != nullptr) + m_jumpTableBlock->localStack().synchronize(stack); + + if (getenv("EVMCC_DEBUG_BLOCKS")) + { + std::ofstream ofs("blocks-sync.dot"); + dumpBasicBlockGraph(ofs); + ofs.close(); + std::cerr << "\n\nAfter stack synchronization \n\n"; + dump(); + } return module; } @@ -879,7 +919,8 @@ void Compiler::compileBasicBlock(BasicBlock& basicBlock, bytesConstRef bytecode, } -void Compiler::linkBasicBlocks() // Stack& stack) + +void Compiler::removeDeadBlocks() { // Remove dead basic blocks auto sthErased = false; @@ -907,187 +948,33 @@ void Compiler::linkBasicBlocks() // Stack& stack) m_jumpTableBlock->llvm()->eraseFromParent(); m_jumpTableBlock.reset(); } - -/* - struct BBInfo - { - BasicBlock& bblock; - std::vector predecessors; - size_t inputItems; - size_t outputItems; - std::vector phisToRewrite; - - BBInfo(BasicBlock& _bblock) - : bblock(_bblock), - predecessors(), - inputItems(_bblock.getStack().initialSize()), - outputItems(_bblock.getStack().size()) - {} - }; - - std::map cfg; - - // Create nodes in cfg - for (auto& pair : this->basicBlocks) - { - auto& bb = pair.second; - cfg.emplace(bb.llvm(), bb); - } - - // Insert jump table block into cfg - if (m_jumpTableBlock) - cfg.emplace(m_jumpTableBlock->llvm(), *m_jumpTableBlock); - - auto& entryBlock = m_mainFunc->getEntryBlock(); - - // Create edges in cfg - for (auto& pair : cfg) - { - auto bbPtr = pair.first; - auto& bbInfo = pair.second; - - for (auto predIt = llvm::pred_begin(bbPtr); predIt != llvm::pred_end(bbPtr); ++predIt) - { - if (*predIt != &entryBlock) - { - auto predInfoEntry = cfg.find(*predIt); - assert(predInfoEntry != cfg.end()); - bbInfo.predecessors.push_back(&predInfoEntry->second); - } - } - } - - // Iteratively compute inputs and outputs of each block, until reaching fixpoint. - bool valuesChanged = true; - while (valuesChanged) - { - valuesChanged = false; - for (auto& pair : cfg) - { - auto& bbInfo = pair.second; - - if (bbInfo.predecessors.empty()) - bbInfo.inputItems = 0; // no consequences for other blocks, so leave valuesChanged false - - for (auto predInfo : bbInfo.predecessors) - { - if (predInfo->outputItems < bbInfo.inputItems) - { - bbInfo.inputItems = predInfo->outputItems; - valuesChanged = true; - } - else if (predInfo->outputItems > bbInfo.inputItems) - { - predInfo->outputItems = bbInfo.inputItems; - valuesChanged = true; - } - } - } - } - - // std::map phiReplacements; - // std::vector phiNodesToRewrite; - - // Propagate values between blocks. - for (auto& pair : cfg) - { - auto llbb = pair.first; - auto& bbInfo = pair.second; - auto& bblock = bbInfo.bblock; - - // Complete phi nodes for the top bbInfo.inputItems placeholder values - auto instrIter = llbb->begin(); - for (size_t valueIdx = 0; valueIdx < bbInfo.inputItems; ++instrIter, ++valueIdx) - { - auto phi = llvm::cast(instrIter); - for (auto predIt : bbInfo.predecessors) - { - assert(valueIdx < predIt->bblock.getStack().size()); - auto value = predIt->bblock.getStack().get(valueIdx); - phi->addIncoming(value, predIt->bblock.llvm()); - } - } - - // Turn the remaining phi nodes into stack.pop's. - // m_builder.SetInsertPoint(llbb, llvm::BasicBlock::iterator(llbb->getFirstNonPHI())); - for (; llvm::isa(*instrIter); ++instrIter) - { - auto phi = llvm::cast(instrIter); - // auto value = stack.popWord(); - // Don't delete the phi node yet. It may still be stored in a local stack of some block. - // phiReplacements[phi] = value; - bbInfo.phisToRewrite.push_back(phi); - } - - // Emit stack push's at the end of the block, just before the terminator; - m_builder.SetInsertPoint(llbb, -- llbb->end()); - auto localStackSize = bblock.getStack().size(); - assert(localStackSize >= bbInfo.outputItems); - for (size_t i = 0; i < localStackSize - bbInfo.outputItems; ++i) - stack.pushWord(bblock.getStack().get(localStackSize - 1 - i)); - } - - for (auto& entry : cfg) - { - // Where was the last stack.pop() inserted - auto lastPopIt = entry.first->begin(); - - for (auto phi : entry.second.phisToRewrite) - { - // Insert stack.pop() before the first use of phi, - // then replace all uses of phi with the popped val. - - if (phi->use_begin() == phi->use_end()) - { - // For a phi with no uses, insert pop just after the previous one - } - std::cout << "*** PHI node " << phi->getName().str() << " has no uses!\n"; - } - else - { - assert(llvm::isa(phi->use_begin()->getUser())); - - m_builder.SetInsertPoint(*phi->use_begin()); - auto popVal = stack.popWord(); - phi->replaceAllUsesWith(popVal); - phi->eraseFromParent(); - } - } - */ } void Compiler::dumpBasicBlockGraph(std::ostream& out) { out << "digraph BB {\n" - << " node [shape=record];\n" + << " node [shape=record, fontname=Courier, fontsize=10];\n" << " entry [share=record, label=\"entry block\"];\n"; -/* + std::vector blocks; - for (auto& pair : this->basicBlocks) + for (auto& pair : basicBlocks) blocks.push_back(&pair.second); if (m_jumpTableBlock) blocks.push_back(m_jumpTableBlock.get()); if (m_badJumpBlock) blocks.push_back(m_badJumpBlock.get()); - std::map phiNodesPerBlock; + // std::map phiNodesPerBlock; // Output nodes for (auto bb : blocks) { std::string blockName = bb->llvm()->getName(); - int numOfPhiNodes = 0; - auto firstNonPhiPtr = bb->llvm()->getFirstNonPHI(); - for (auto instrIter = bb->llvm()->begin(); &*instrIter != firstNonPhiPtr; ++instrIter, ++numOfPhiNodes); - phiNodesPerBlock[bb] = numOfPhiNodes; - - auto initStackSize = bb->getStack().initialSize(); - auto endStackSize = bb->getStack().size(); + std::ostringstream oss; + bb->dump(oss, true); - out << " \"" << blockName << "\" [shape=record, label=\"" - << initStackSize << "|" << blockName << "|" << endStackSize - << "\"];\n"; + out << " \"" << blockName << "\" [shape=record, label=\" { " << blockName << "|" << oss.str() << "} \"];\n"; } // Output edges @@ -1100,14 +987,21 @@ void Compiler::dumpBasicBlockGraph(std::ostream& out) { out << " \"" << (*it)->getName().str() << "\" -> \"" << blockName << "\" [" << ((m_jumpTableBlock.get() && *it == m_jumpTableBlock.get()->llvm()) ? "style = dashed, " : "") - << "label = \"" - << phiNodesPerBlock[bb] - << "\"];\n"; + //<< "label = \"" + //<< phiNodesPerBlock[bb] + << "];\n"; } } out << "}\n"; - */ +} + +void Compiler::dump() +{ + for (auto& entry : basicBlocks) + entry.second.dump(); + if (m_jumpTableBlock != nullptr) + m_jumpTableBlock->dump(); } } diff --git a/libevmjit/Compiler.h b/libevmjit/Compiler.h index 9d7b37fbc..afd5aef72 100644 --- a/libevmjit/Compiler.h +++ b/libevmjit/Compiler.h @@ -33,8 +33,10 @@ private: void compileBasicBlock(BasicBlock& basicBlock, bytesConstRef bytecode, class Memory& memory, class Ext& ext, class GasMeter& gasMeter, llvm::BasicBlock* nextBasicBlock); - void linkBasicBlocks(); //class Stack& stack); + void removeDeadBlocks(); + /// Dump all basic blocks to stderr. Useful in a debugging session. + void dump(); llvm::IRBuilder<> m_builder;