diff --git a/CodingStandards.txt b/CodingStandards.txt index 66a61f6ae..a065928d3 100644 --- a/CodingStandards.txt +++ b/CodingStandards.txt @@ -196,7 +196,7 @@ a. Prefer 'using' to 'typedef'. e.g. using ints = std::vector; rather than b. Generally avoid shortening a standard form that already includes all important information: - e.g. stick to shared_ptr rather than shortening to ptr. c. Where there are exceptions to this (due to excessive use and clear meaning), note the change prominently and use it consistently. -- e.g. using Guard = boost::lock_guard; ///< Guard is used throughout the codebase since it's clear in meaning and used commonly. +- e.g. using Guard = std::lock_guard; ///< Guard is used throughout the codebase since it's clear in meaning and used commonly. d. In general expressions should be roughly as important/semantically meaningful as the space they occupy. @@ -226,4 +226,3 @@ a. Includes should go in order of lower level (STL -> boost -> libdevcore -> lib #include b. The only exception to the above rule is the top of a .cpp file where its corresponding header should be located. - diff --git a/alethzero/Main.ui b/alethzero/Main.ui index e12756d6e..2515959ad 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -132,7 +132,7 @@ 0 0 1617 - 24 + 25 @@ -192,8 +192,11 @@ + + + + - @@ -1697,17 +1700,6 @@ font-size: 14pt &Clear Pending - - - true - - - false - - - Use &LLVM-EVM - - &Kill Account @@ -1783,6 +1775,30 @@ font-size: 14pt &Gas Prices... + + + true + + + Interpreter + + + + + true + + + JIT + + + + + true + + + Smart + + &Sentinel... diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 445b5fdac..d7fc0b409 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -239,6 +239,21 @@ Main::Main(QWidget *parent) : ethereum()->setDefault(LatestBlock); + m_vmSelectionGroup = new QActionGroup{ui->menu_Debug}; + m_vmSelectionGroup->addAction(ui->vmInterpreter); + m_vmSelectionGroup->addAction(ui->vmJIT); + m_vmSelectionGroup->addAction(ui->vmSmart); + m_vmSelectionGroup->setExclusive(true); + +#if ETH_EVMJIT + ui->vmSmart->setChecked(true); // Default when JIT enabled + on_vmSmart_triggered(); +#else + ui->vmInterpreter->setChecked(true); + ui->vmJIT->setEnabled(false); + ui->vmSmart->setEnabled(false); +#endif + readSettings(); m_transact = new Transact(this, this); @@ -247,10 +262,6 @@ Main::Main(QWidget *parent) : #if !ETH_FATDB removeDockWidget(ui->dockWidget_accounts); -#endif -#if !ETH_EVMJIT - ui->jitvm->setEnabled(false); - ui->jitvm->setChecked(false); #endif installWatches(); startTimer(100); @@ -731,7 +742,8 @@ void Main::writeSettings() s.setValue("url", ui->urlEdit->text()); s.setValue("privateChain", m_privateChain); s.setValue("verbosity", ui->verbosity->value()); - s.setValue("jitvm", ui->jitvm->isChecked()); + if (auto vm = m_vmSelectionGroup->checkedAction()) + s.setValue("vm", vm->text()); bytes d = m_webThree->saveNetwork(); if (!d.empty()) @@ -822,8 +834,28 @@ void Main::readSettings(bool _skipGeometry) m_privateChain = s.value("privateChain", "").toString(); ui->usePrivate->setChecked(m_privateChain.size()); ui->verbosity->setValue(s.value("verbosity", 1).toInt()); - ui->jitvm->setChecked(s.value("jitvm", true).toBool()); - on_jitvm_triggered(); + +#if ETH_EVMJIT // We care only if JIT is enabled. Otherwise it can cause misconfiguration. + auto vmName = s.value("vm").toString(); + if (!vmName.isEmpty()) + { + if (vmName == ui->vmInterpreter->text()) + { + ui->vmInterpreter->setChecked(true); + on_vmInterpreter_triggered(); + } + else if (vmName == ui->vmJIT->text()) + { + ui->vmJIT->setChecked(true); + on_vmJIT_triggered(); + } + else if (vmName == ui->vmSmart->text()) + { + ui->vmSmart->setChecked(true); + on_vmSmart_triggered(); + } + } +#endif ui->urlEdit->setText(s.value("url", "about:blank").toString()); //http://gavwood.com/gavcoin.html on_urlEdit_returnPressed(); @@ -1000,11 +1032,9 @@ void Main::on_usePrivate_triggered() on_killBlockchain_triggered(); } -void Main::on_jitvm_triggered() -{ - bool jit = ui->jitvm->isChecked(); - VMFactory::setKind(jit ? VMKind::JIT : VMKind::Interpreter); -} +void Main::on_vmInterpreter_triggered() { VMFactory::setKind(VMKind::Interpreter); } +void Main::on_vmJIT_triggered() { VMFactory::setKind(VMKind::JIT); } +void Main::on_vmSmart_triggered() { VMFactory::setKind(VMKind::Smart); } void Main::on_urlEdit_returnPressed() { diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index a3f6328cd..b1939534b 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -44,6 +44,7 @@ #include "Connect.h" class QListWidgetItem; +class QActionGroup; namespace Ui { class Main; @@ -182,8 +183,10 @@ private slots: void on_forceMining_triggered(); void on_usePrivate_triggered(); void on_turboMining_triggered(); - void on_jitvm_triggered(); void on_retryUnknown_triggered(); + void on_vmInterpreter_triggered(); + void on_vmJIT_triggered(); + void on_vmSmart_triggered(); // Debugger void on_debugCurrent_triggered(); @@ -272,6 +275,8 @@ private: dev::Address m_nameReg; dev::Address m_beneficiary; + QActionGroup* m_vmSelectionGroup = nullptr; + QList> m_consoleHistory; QMutex m_logLock; QString m_logHistory; @@ -287,6 +292,6 @@ private: std::unique_ptr m_dappHost; DappLoader* m_dappLoader; QWebEnginePage* m_webPage; - + Connect m_connect; }; diff --git a/eth/main.cpp b/eth/main.cpp index e60da08f5..7d269d944 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -194,7 +194,7 @@ void help() << "General Options:" << endl << " -d,--db-path Load database from path (default: " << getDataDir() << ")" << endl #if ETH_EVMJIT || !ETH_TRUE - << " -J,--jit Enable EVM JIT (default: off)." << endl + << " --vm Select VM. Options are: interpreter, jit, smart. (default: interpreter)" << endl #endif << " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (default: 8)." << endl << " -V,--version Show the version and exit." << endl @@ -1441,8 +1441,21 @@ int main(int argc, char** argv) } } #if ETH_EVMJIT - else if (arg == "-J" || arg == "--jit") - jit = true; + else if (arg == "--vm" && i + 1 < argc) + { + string vmKind = argv[++i]; + if (vmKind == "interpreter") + VMFactory::setKind(VMKind::Interpreter); + else if (vmKind == "jit") + VMFactory::setKind(VMKind::JIT); + else if (vmKind == "smart") + VMFactory::setKind(VMKind::Smart); + else + { + cerr << "Unknown VM kind: " << vmKind << endl; + return -1; + } + } #endif else if (arg == "-h" || arg == "--help") help(); @@ -1479,7 +1492,7 @@ int main(int argc, char** argv) g_logPost = [&](std::string const& a, char const*){ static SpinLock s_lock; SpinGuard l(s_lock); - + if (g_silence) logbuf += a + "\n"; else @@ -1764,4 +1777,3 @@ int main(int argc, char** argv) writeFile((dbPath.size() ? dbPath : getDataDir()) + "/network.rlp", netData); return 0; } - diff --git a/ethvm/main.cpp b/ethvm/main.cpp index 4ca733ed0..46442d4ee 100644 --- a/ethvm/main.cpp +++ b/ethvm/main.cpp @@ -46,8 +46,7 @@ void help() #if ETH_EVMJIT || !ETH_TRUE << endl << "VM options:" << endl - << " -J,--jit Enable LLVM VM (default: off)." << endl - << " --smart Enable smart VM (default: off)." << endl + << " --vm Select VM. Options are: interpreter, jit, smart. (default: interpreter)" << endl #endif << endl << "Options for trace:" << endl @@ -97,10 +96,21 @@ int main(int argc, char** argv) else if (arg == "-V" || arg == "--version") version(); #if ETH_EVMJIT - else if (arg == "-J" || arg == "--jit") - VMFactory::setKind(VMKind::JIT); - else if (arg == "--smart") - VMFactory::setKind(VMKind::Smart); + else if (arg == "--vm" && i + 1 < argc) + { + string vmKind = argv[++i]; + if (vmKind == "interpreter") + VMFactory::setKind(VMKind::Interpreter); + else if (vmKind == "jit") + VMFactory::setKind(VMKind::JIT); + else if (vmKind == "smart") + VMFactory::setKind(VMKind::Smart); + else + { + cerr << "Unknown VM kind: " << vmKind << endl; + return -1; + } + } #endif else if (arg == "--mnemonics") st.setShowMnemonics(); diff --git a/evmjit/include/evmjit/JIT.h b/evmjit/include/evmjit/JIT.h index fcb0db4e7..901c351d9 100644 --- a/evmjit/include/evmjit/JIT.h +++ b/evmjit/include/evmjit/JIT.h @@ -107,9 +107,7 @@ enum class ReturnCode Rejected = -5, ///< Input data (code, gas, block info, etc.) does not meet JIT requirement and execution request has been rejected // Internal error codes - LLVMConfigError = -101, - LLVMCompileError = -102, - LLVMLinkError = -103, + LLVMError = -101, UnexpectedException = -111, @@ -155,6 +153,9 @@ public: /// \param _codeHash The Keccak hash of the EVM code. EXPORT static bool isCodeReady(h256 const& _codeHash); + /// Compile the given EVM code to machine code and make available for execution. + EXPORT static void compile(byte const* _code, uint64_t _codeSize, h256 const& _codeHash); + EXPORT static ReturnCode exec(ExecutionContext& _context); }; diff --git a/evmjit/libevmjit/JIT.cpp b/evmjit/libevmjit/JIT.cpp index 6a9b7f932..53c36dcb2 100644 --- a/evmjit/libevmjit/JIT.cpp +++ b/evmjit/libevmjit/JIT.cpp @@ -1,6 +1,7 @@ #include "evmjit/JIT.h" #include +#include #include "preprocessor/llvm_includes_start.h" #include @@ -82,6 +83,7 @@ void parseOptions() class JITImpl { std::unique_ptr m_engine; + mutable std::mutex x_codeMap; std::unordered_map m_codeMap; public: @@ -97,6 +99,8 @@ public: ExecFunc getExecFunc(h256 const& _codeHash) const; void mapExecFunc(h256 _codeHash, ExecFunc _funcAddr); + + ExecFunc compile(byte const* _code, uint64_t _codeSize, h256 const& _codeHash); }; JITImpl::JITImpl() @@ -134,6 +138,7 @@ JITImpl::JITImpl() ExecFunc JITImpl::getExecFunc(h256 const& _codeHash) const { + std::lock_guard lock{x_codeMap}; auto it = m_codeMap.find(_codeHash); if (it != m_codeMap.end()) return it->second; @@ -142,9 +147,37 @@ ExecFunc JITImpl::getExecFunc(h256 const& _codeHash) const void JITImpl::mapExecFunc(h256 _codeHash, ExecFunc _funcAddr) { + std::lock_guard lock{x_codeMap}; m_codeMap.emplace(std::move(_codeHash), _funcAddr); } +ExecFunc JITImpl::compile(byte const* _code, uint64_t _codeSize, h256 const& _codeHash) +{ + auto name = hash2str(_codeHash); + auto module = Cache::getObject(name); + if (!module) + { + // TODO: Listener support must be redesigned. These should be a feature of JITImpl + //listener->stateChanged(ExecState::Compilation); + assert(_code || !_codeSize); //TODO: Is it good idea to execute empty code? + module = Compiler{{}}.compile(_code, _code + _codeSize, name); + + if (g_optimize) + { + //listener->stateChanged(ExecState::Optimization); + optimize(*module); + } + + prepare(*module); + } + if (g_dump) + module->dump(); + + m_engine->addModule(std::move(module)); + //listener->stateChanged(ExecState::CodeGen); + return (ExecFunc)m_engine->getFunctionAddress(name); +} + } // anonymous namespace bool JIT::isCodeReady(h256 const& _codeHash) @@ -152,62 +185,41 @@ bool JIT::isCodeReady(h256 const& _codeHash) return JITImpl::instance().getExecFunc(_codeHash) != nullptr; } -ReturnCode JIT::exec(ExecutionContext& _context) +void JIT::compile(byte const* _code, uint64_t _codeSize, h256 const& _codeHash) { auto& jit = JITImpl::instance(); + auto execFunc = jit.compile(_code, _codeSize, _codeHash); + if (execFunc) // FIXME: What with error? + jit.mapExecFunc(_codeHash, execFunc); +} - std::unique_ptr listener{new ExecStats}; - listener->stateChanged(ExecState::Started); +ReturnCode JIT::exec(ExecutionContext& _context) +{ + //std::unique_ptr listener{new ExecStats}; + //listener->stateChanged(ExecState::Started); + //static StatsCollector statsCollector; - auto code = _context.code(); - auto codeSize = _context.codeSize(); + auto& jit = JITImpl::instance(); auto codeHash = _context.codeHash(); - - static StatsCollector statsCollector; - - auto mainFuncName = hash2str(codeHash); - - // TODO: Remove cast auto execFunc = jit.getExecFunc(codeHash); if (!execFunc) { - auto module = Cache::getObject(mainFuncName); - if (!module) - { - listener->stateChanged(ExecState::Compilation); - assert(code || !codeSize); //TODO: Is it good idea to execute empty code? - module = Compiler{{}}.compile(code, code + codeSize, mainFuncName); - - if (g_optimize) - { - listener->stateChanged(ExecState::Optimization); - optimize(*module); - } - - prepare(*module); - } - if (g_dump) - module->dump(); - - jit.engine().addModule(std::move(module)); - listener->stateChanged(ExecState::CodeGen); - execFunc = (ExecFunc)jit.engine().getFunctionAddress(mainFuncName); - if (!CHECK(execFunc)) - return ReturnCode::LLVMLinkError; + execFunc = jit.compile(_context.code(), _context.codeSize(), codeHash); + if (!execFunc) + return ReturnCode::LLVMError; jit.mapExecFunc(codeHash, execFunc); } - listener->stateChanged(ExecState::Execution); + //listener->stateChanged(ExecState::Execution); auto returnCode = execFunc(&_context); - listener->stateChanged(ExecState::Return); + //listener->stateChanged(ExecState::Return); if (returnCode == ReturnCode::Return) _context.returnData = _context.getReturnData(); // Save reference to return data - listener->stateChanged(ExecState::Finished); - - if (g_stats) - statsCollector.stats.push_back(std::move(listener)); + //listener->stateChanged(ExecState::Finished); + // if (g_stats) + // statsCollector.stats.push_back(std::move(listener)); return returnCode; } diff --git a/libevm/SmartVM.cpp b/libevm/SmartVM.cpp index f8785b6df..add43302e 100644 --- a/libevm/SmartVM.cpp +++ b/libevm/SmartVM.cpp @@ -20,8 +20,13 @@ #include "SmartVM.h" #include +#include +#include +#include +#include #include #include +#include #include #include #include "VMFactory.h" @@ -32,6 +37,8 @@ namespace eth { namespace { + struct JitInfo: LogChannel { static const char* name() { return "JIT"; }; static const int verbosity = 11; }; + using HitMap = std::unordered_map; HitMap& getHitMap() @@ -39,30 +46,93 @@ namespace static HitMap s_hitMap; return s_hitMap; } + + struct JitTask + { + bytes code; + h256 codeHash; + }; + + class JitWorker + { + bool m_finished = false; + std::mutex x_mutex; + std::condition_variable m_cv; + std::thread m_worker; + std::queue m_queue; + + bool pop(JitTask& o_task) + { + std::unique_lock lock{x_mutex}; + m_cv.wait(lock, [this]{ return m_finished || !m_queue.empty(); }); + if (m_finished) + return false; + + assert(!m_queue.empty()); + o_task = std::move(m_queue.front()); + m_queue.pop(); + return true; + } + + void work() + { + clog(JitInfo) << "JIT worker started."; + JitTask task; + while (pop(task)) + { + clog(JitInfo) << "Compilation... " << task.codeHash; + evmjit::JIT::compile(task.code.data(), task.code.size(), eth2jit(task.codeHash)); + clog(JitInfo) << " ...finished " << task.codeHash; + } + clog(JitInfo) << "JIT worker finished."; + } + + public: + JitWorker() noexcept: m_worker([this]{ work(); }) + {} + + ~JitWorker() + { + DEV_GUARDED(x_mutex) + m_finished = true; + m_cv.notify_one(); + m_worker.join(); + } + + void push(JitTask&& _task) + { + DEV_GUARDED(x_mutex) + m_queue.push(std::move(_task)); + m_cv.notify_one(); + } + }; } bytesConstRef SmartVM::execImpl(u256& io_gas, ExtVMFace& _ext, OnOpFunc const& _onOp) { - auto codeHash = sha3(_ext.code); + auto codeHash = _ext.codeHash; auto vmKind = VMKind::Interpreter; // default VM // Jitted EVM code already in memory? if (evmjit::JIT::isCodeReady(eth2jit(codeHash))) { - cnote << "Jitted"; + clog(JitInfo) << "JIT: " << codeHash; vmKind = VMKind::JIT; } else { + static JitWorker s_worker; + // Check EVM code hit count - static const uint64_t c_hitTreshold = 1; + static const uint64_t c_hitTreshold = 2; auto& hits = getHitMap()[codeHash]; ++hits; - if (hits > c_hitTreshold) + if (hits == c_hitTreshold) { - cnote << "JIT selected"; - vmKind = VMKind::JIT; + clog(JitInfo) << "Schedule: " << codeHash; + s_worker.push({_ext.code, codeHash}); } + clog(JitInfo) << "Interpreter: " << codeHash; } // TODO: Selected VM must be kept only because it returns reference to its internal memory. diff --git a/neth/main.cpp b/neth/main.cpp index a55c3d559..65f46735f 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -101,7 +101,7 @@ void help() << " -x,--peers Attempt to connect to given number of peers (default: 5)." << endl << " -V,--version Show the version and exit." << endl #if ETH_EVMJIT - << " --jit Use EVM JIT (default: off)." << endl + << " --vm Select VM. Options are: interpreter, jit, smart. (default: interpreter)" << endl #endif ; exit(0); @@ -514,15 +514,23 @@ int main(int argc, char** argv) return -1; } } - else if (arg == "--jit") - { #if ETH_EVMJIT - jit = true; -#else - cerr << "EVM JIT not enabled" << endl; - return -1; -#endif + else if (arg == "--vm" && i + 1 < argc) + { + string vmKind = argv[++i]; + if (vmKind == "interpreter") + VMFactory::setKind(VMKind::Interpreter); + else if (vmKind == "jit") + VMFactory::setKind(VMKind::JIT); + else if (vmKind == "smart") + VMFactory::setKind(VMKind::Smart); + else + { + cerr << "Unknown VM kind: " << vmKind << endl; + return -1; + } } +#endif else if (arg == "-h" || arg == "--help") help(); else if (arg == "-V" || arg == "--version") @@ -551,7 +559,7 @@ int main(int argc, char** argv) mode == NodeMode::Full ? set{"eth", "shh"} : set(), netPrefs, &nodesState); - + web3.setIdealPeerCount(peers); std::shared_ptr gasPricer = make_shared(u256(double(ether / 1000) / etherPrice), u256(blockFees * 1000)); eth::Client* c = mode == NodeMode::Full ? web3.ethereum() : nullptr; diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp index 698f35122..b3dee72ec 100644 --- a/test/TestHelper.cpp +++ b/test/TestHelper.cpp @@ -235,7 +235,7 @@ void ImportTest::importState(json_spirit::mObject& _o, State& _state) } void ImportTest::importTransaction(json_spirit::mObject& _o) -{ +{ if (_o.count("secretKey") > 0) { assert(_o.count("nonce") > 0); @@ -728,10 +728,20 @@ Options::Options() for (auto i = 0; i < argc; ++i) { auto arg = std::string{argv[i]}; - if (arg == "--jit") - eth::VMFactory::setKind(eth::VMKind::JIT); - else if (arg == "--vm=smart") - eth::VMFactory::setKind(eth::VMKind::Smart); + if (arg == "--vm" && i + 1 < argc) + { + string vmKind = argv[++i]; + if (vmKind == "interpreter") + VMFactory::setKind(VMKind::Interpreter); + else if (vmKind == "jit") + VMFactory::setKind(VMKind::JIT); + else if (vmKind == "smart") + VMFactory::setKind(VMKind::Smart); + else + cerr << "Unknown VM kind: " << vmKind << endl; + } + else if (arg == "--jit") // TODO: Remove deprecated option "--jit" + VMFactory::setKind(VMKind::JIT); else if (arg == "--vmtrace") vmtrace = true; else if (arg == "--filltests")