Browse Source

Merge pull request #1466 from imapp-pl/pr/jit_cache

EVM JIT stack limit support
cl-refactor
Gav Wood 10 years ago
parent
commit
eaf6c65cfe
  1. 1
      evmjit/libevmjit/BasicBlock.cpp
  2. 4
      evmjit/libevmjit/BasicBlock.h
  3. 31
      evmjit/libevmjit/Cache.cpp
  4. 12
      evmjit/libevmjit/Cache.h
  5. 3
      evmjit/libevmjit/Compiler.cpp
  6. 17
      evmjit/libevmjit/ExecutionEngine.cpp
  7. 44
      evmjit/libevmjit/RuntimeManager.cpp
  8. 5
      evmjit/libevmjit/RuntimeManager.h
  9. 33
      test/Stats.cpp
  10. 13
      test/Stats.h
  11. 19
      test/TestHelper.cpp
  12. 4
      test/TestHelper.h
  13. 5
      test/state.cpp
  14. 5
      test/vm.cpp

1
evmjit/libevmjit/BasicBlock.cpp

@ -49,6 +49,7 @@ void BasicBlock::LocalStack::push(llvm::Value* _value)
assert(_value->getType() == Type::Word);
m_bblock.m_currentStack.push_back(_value);
m_bblock.m_tosOffset += 1;
m_maxSize = std::max(m_maxSize, m_bblock.m_currentStack.size());
}
llvm::Value* BasicBlock::LocalStack::pop()

4
evmjit/libevmjit/BasicBlock.h

@ -33,6 +33,9 @@ public:
/// @param _index Index of value to be swaped. Must be > 0.
void swap(size_t _index);
size_t getMaxSize() const { return m_maxSize; }
int getDiff() const { return m_bblock.m_tosOffset; }
private:
LocalStack(BasicBlock& _owner);
LocalStack(LocalStack const&) = delete;
@ -49,6 +52,7 @@ public:
private:
BasicBlock& m_bblock;
size_t m_maxSize = 0; ///< Max size reached by the stack.
};
explicit BasicBlock(instr_idx _firstInstrIdx, code_iterator _begin, code_iterator _end, llvm::Function* _mainFunc, llvm::IRBuilder<>& _builder, bool isJumpDest);

31
evmjit/libevmjit/Cache.cpp

@ -4,6 +4,7 @@
#include <llvm/IR/Module.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Instructions.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/Support/Path.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/raw_os_ostream.h>
@ -59,6 +60,36 @@ void Cache::clear()
fs::remove(it->path());
}
void Cache::preload(llvm::ExecutionEngine& _ee, std::unordered_map<std::string, uint64_t>& _funcCache)
{
// TODO: Cache dir should be in one place
using namespace llvm::sys;
llvm::SmallString<256> cachePath;
path::system_temp_directory(false, cachePath);
path::append(cachePath, "evm_objs");
// Disable listener
auto listener = g_listener;
g_listener = nullptr;
std::error_code err;
for (auto it = fs::directory_iterator{cachePath.str(), err}; it != fs::directory_iterator{}; it.increment(err))
{
auto name = it->path().substr(cachePath.size() + 1);
if (auto module = getObject(name))
{
DLOG(cache) << "Preload: " << name << "\n";
_ee.addModule(module.get());
module.release();
auto addr = _ee.getFunctionAddress(name);
assert(addr);
_funcCache[std::move(name)] = addr;
}
}
g_listener = listener;
}
std::unique_ptr<llvm::Module> Cache::getObject(std::string const& id)
{
if (g_mode != CacheMode::on && g_mode != CacheMode::read)

12
evmjit/libevmjit/Cache.h

@ -1,9 +1,15 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <llvm/ExecutionEngine/ObjectCache.h>
namespace llvm
{
class ExecutionEngine;
}
namespace dev
{
namespace eth
@ -18,7 +24,8 @@ enum class CacheMode
off,
read,
write,
clear
clear,
preload
};
class ObjectCache : public llvm::ObjectCache
@ -43,6 +50,9 @@ public:
/// Clears cache storage
static void clear();
/// Loads all available cached objects to ExecutionEngine
static void preload(llvm::ExecutionEngine& _ee, std::unordered_map<std::string, uint64_t>& _funcCache);
};
}

3
evmjit/libevmjit/Compiler.cpp

@ -838,6 +838,9 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti
// Block may have no terminator if the next instruction is a jump destination.
if (!_basicBlock.llvm()->getTerminator())
m_builder.CreateBr(_nextBasicBlock);
m_builder.SetInsertPoint(_basicBlock.llvm()->getFirstNonPHI());
_runtimeManager.checkStackLimit(_basicBlock.localStack().getMaxSize(), _basicBlock.localStack().getDiff());
}

17
evmjit/libevmjit/ExecutionEngine.cpp

@ -76,6 +76,7 @@ cl::opt<CacheMode> g_cache{"cache", cl::desc{"Cache compiled EVM code on disk"},
clEnumValN(CacheMode::read, "r", "Read only. No new objects are added to cache."),
clEnumValN(CacheMode::write, "w", "Write only. No objects are loaded from cache."),
clEnumValN(CacheMode::clear, "c", "Clear the cache storage. Cache is disabled."),
clEnumValN(CacheMode::preload, "p", "Preload all cached objects."),
clEnumValEnd)};
cl::opt<bool> g_stats{"st", cl::desc{"Statistics"}};
cl::opt<bool> g_dump{"dump", cl::desc{"Dump LLVM IR module"}};
@ -111,8 +112,15 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env)
std::unique_ptr<ExecStats> listener{new ExecStats};
listener->stateChanged(ExecState::Started);
bool preloadCache = g_cache == CacheMode::preload;
if (preloadCache)
g_cache = CacheMode::on;
// TODO: Do not pseudo-init the cache every time
auto objectCache = (g_cache != CacheMode::off && g_cache != CacheMode::clear) ? Cache::getObjectCache(g_cache, listener.get()) : nullptr;
static std::unordered_map<std::string, uint64_t> funcCache;
static std::unique_ptr<llvm::ExecutionEngine> ee;
if (!ee)
{
@ -138,6 +146,9 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env)
return ReturnCode::LLVMConfigError;
module.release(); // Successfully created llvm::ExecutionEngine takes ownership of the module
ee->setObjectCache(objectCache);
if (preloadCache)
Cache::preload(*ee, funcCache);
}
static StatsCollector statsCollector;
@ -146,10 +157,9 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env)
m_runtime.init(_data, _env);
EntryFuncPtr entryFuncPtr = nullptr;
static std::unordered_map<std::string, EntryFuncPtr> funcCache;
auto it = funcCache.find(mainFuncName);
if (it != funcCache.end())
entryFuncPtr = it->second;
entryFuncPtr = (EntryFuncPtr) it->second;
if (!entryFuncPtr)
{
@ -177,7 +187,8 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env)
if (!CHECK(entryFuncPtr))
return ReturnCode::LLVMLinkError;
funcCache[mainFuncName] = entryFuncPtr;
if (it == funcCache.end())
funcCache[mainFuncName] = (uint64_t) entryFuncPtr;
listener->stateChanged(ExecState::Execution);
auto returnCode = entryFuncPtr(&m_runtime);

44
evmjit/libevmjit/RuntimeManager.cpp

@ -78,7 +78,7 @@ llvm::Twine getName(RuntimeData::Index _index)
case RuntimeData::CodeSize: return "code";
case RuntimeData::CallDataSize: return "callDataSize";
case RuntimeData::Gas: return "gas";
case RuntimeData::Number: return "number";
case RuntimeData::Number: return "number";
case RuntimeData::Timestamp: return "timestamp";
}
}
@ -101,6 +101,48 @@ RuntimeManager::RuntimeManager(llvm::IRBuilder<>& _builder, code_iterator _codeB
assert(m_memPtr->getType() == Array::getType()->getPointerTo());
m_envPtr = m_builder.CreateLoad(m_builder.CreateStructGEP(rtPtr, 1), "env");
assert(m_envPtr->getType() == Type::EnvPtr);
m_stackSize = m_builder.CreateAlloca(Type::Size, nullptr, "stackSize");
m_builder.CreateStore(m_builder.getInt64(0), m_stackSize);
llvm::Type* checkStackLimitArgs[] = {Type::Size->getPointerTo(), Type::Size, Type::Size, Type::BytePtr};
m_checkStackLimit = llvm::Function::Create(llvm::FunctionType::get(Type::Void, checkStackLimitArgs, false), llvm::Function::PrivateLinkage, "stack.checkSize", getModule());
m_checkStackLimit->setDoesNotThrow();
m_checkStackLimit->setDoesNotCapture(1);
auto checkBB = llvm::BasicBlock::Create(_builder.getContext(), "Check", m_checkStackLimit);
auto updateBB = llvm::BasicBlock::Create(_builder.getContext(), "Update", m_checkStackLimit);
auto outOfStackBB = llvm::BasicBlock::Create(_builder.getContext(), "OutOfStack", m_checkStackLimit);
auto currSizePtr = &m_checkStackLimit->getArgumentList().front();
currSizePtr->setName("currSize");
auto max = currSizePtr->getNextNode();
max->setName("max");
auto diff = max->getNextNode();
diff->setName("diff");
auto jmpBuf = diff->getNextNode();
jmpBuf->setName("jmpBuf");
InsertPointGuard guard{m_builder};
m_builder.SetInsertPoint(checkBB);
auto currSize = m_builder.CreateLoad(currSizePtr, "cur");
auto maxSize = m_builder.CreateNUWAdd(currSize, max, "maxSize");
auto ok = m_builder.CreateICmpULE(maxSize, m_builder.getInt64(1024), "ok");
m_builder.CreateCondBr(ok, updateBB, outOfStackBB, Type::expectTrue);
m_builder.SetInsertPoint(updateBB);
auto newSize = m_builder.CreateNSWAdd(currSize, diff);
m_builder.CreateStore(newSize, currSizePtr);
m_builder.CreateRetVoid();
m_builder.SetInsertPoint(outOfStackBB);
abort(jmpBuf);
m_builder.CreateUnreachable();
}
void RuntimeManager::checkStackLimit(size_t _max, int _diff)
{
createCall(m_checkStackLimit, {m_stackSize, m_builder.getInt64(_max), m_builder.getInt64(_diff), getJmpBuf()});
}
llvm::Value* RuntimeManager::getRuntimePtr()

5
evmjit/libevmjit/RuntimeManager.h

@ -48,6 +48,8 @@ public:
static llvm::StructType* getRuntimeType();
static llvm::StructType* getRuntimeDataType();
void checkStackLimit(size_t _max, int _diff);
private:
llvm::Value* getPtr(RuntimeData::Index _index);
void set(RuntimeData::Index _index, llvm::Value* _value);
@ -59,6 +61,9 @@ private:
llvm::Value* m_memPtr = nullptr;
llvm::Value* m_envPtr = nullptr;
llvm::Value* m_stackSize = nullptr;
llvm::Function* m_checkStackLimit = nullptr;
code_iterator m_codeBegin = {};
code_iterator m_codeEnd = {};

33
test/Stats.cpp

@ -19,6 +19,7 @@
#include <iterator>
#include <numeric>
#include <fstream>
namespace dev
{
@ -31,6 +32,11 @@ Stats& Stats::get()
return instance;
}
void Stats::suiteStarted(std::string const& _name)
{
m_currentSuite = _name;
}
void Stats::testStarted(std::string const& _name)
{
m_currentTest = _name;
@ -39,7 +45,7 @@ void Stats::testStarted(std::string const& _name)
void Stats::testFinished()
{
m_stats[clock::now() - m_tp] = std::move(m_currentTest);
m_stats.push_back({clock::now() - m_tp, m_currentSuite + "/" + m_currentTest});
}
std::ostream& operator<<(std::ostream& out, Stats::clock::duration const& d)
@ -52,31 +58,42 @@ Stats::~Stats()
if (m_stats.empty())
return;
std::sort(m_stats.begin(), m_stats.end(), [](Stats::Item const& a, Stats::Item const& b){
return a.duration < b.duration;
});
auto& out = std::cout;
auto itr = m_stats.begin();
auto min = *itr;
auto max = *m_stats.rbegin();
std::advance(itr, m_stats.size() / 2);
auto med = *itr;
auto tot = std::accumulate(m_stats.begin(), m_stats.end(), clock::duration{}, [](clock::duration const& a, stats_t::value_type const& v)
auto tot = std::accumulate(m_stats.begin(), m_stats.end(), clock::duration{}, [](clock::duration const& a, Stats::Item const& v)
{
return a + v.first;
return a + v.duration;
});
out << "\nSTATS:\n\n" << std::setfill(' ');
if (Options::get().statsFull)
if (Options::get().statsOutFile == "out")
{
for (auto&& s: m_stats)
out << " " << std::setw(40) << std::left << s.second.substr(0, 40) << s.first << " \n";
out << " " << std::setw(40) << std::left << s.name.substr(0, 40) << s.duration << " \n";
out << "\n";
}
else if (!Options::get().statsOutFile.empty())
{
// Output stats to file
std::ofstream file{Options::get().statsOutFile};
for (auto&& s: m_stats)
file << s.name << "\t" << std::chrono::duration_cast<std::chrono::microseconds>(s.duration).count() << "\n";
}
out << " tot: " << tot << "\n"
<< " avg: " << (tot / m_stats.size()) << "\n\n"
<< " min: " << min.first << " (" << min.second << ")\n"
<< " med: " << med.first << " (" << med.second << ")\n"
<< " max: " << max.first << " (" << max.second << ")\n";
<< " min: " << min.duration << " (" << min.name << ")\n"
<< " med: " << med.duration << " (" << med.name << ")\n"
<< " max: " << max.duration << " (" << max.name << ")\n";
}
}

13
test/Stats.h

@ -18,7 +18,7 @@
#pragma once
#include <chrono>
#include <map>
#include <vector>
#include "TestHelper.h"
@ -31,19 +31,26 @@ class Stats: public Listener
{
public:
using clock = std::chrono::high_resolution_clock;
using stats_t = std::map<clock::duration, std::string>;
struct Item
{
clock::duration duration;
std::string name;
};
static Stats& get();
~Stats();
void suiteStarted(std::string const& _name) override;
void testStarted(std::string const& _name) override;
void testFinished() override;
private:
clock::time_point m_tp;
std::string m_currentSuite;
std::string m_currentTest;
stats_t m_stats;
std::vector<Item> m_stats;
};
}

19
test/TestHelper.cpp

@ -29,6 +29,7 @@
#include <libethereum/Client.h>
#include <liblll/Compiler.h>
#include <libevm/VMFactory.h>
#include "Stats.h"
using namespace std;
using namespace dev::eth;
@ -431,6 +432,9 @@ void executeTests(const string& _name, const string& _testPathAppendix, std::fun
string testPath = getTestPath();
testPath += _testPathAppendix;
if (Options::get().stats)
Listener::registerListener(Stats::get());
if (Options::get().fillTests)
{
try
@ -462,6 +466,7 @@ void executeTests(const string& _name, const string& _testPathAppendix, std::fun
string s = asString(dev::contents(testPath + "/" + _name + ".json"));
BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of " + testPath + "/" + _name + ".json is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?");
json_spirit::read_string(s, v);
Listener::notifySuiteStarted(_name);
doTests(v, false);
}
catch (Exception const& _e)
@ -535,10 +540,12 @@ Options::Options()
vmtrace = true;
else if (arg == "--filltests")
fillTests = true;
else if (arg == "--stats")
else if (arg.compare(0, 7, "--stats") == 0)
{
stats = true;
else if (arg == "--stats=full")
stats = statsFull = true;
if (arg.size() > 7)
statsOutFile = arg.substr(8); // skip '=' char
}
else if (arg == "--performance")
performance = true;
else if (arg == "--quadratic")
@ -586,6 +593,12 @@ void Listener::registerListener(Listener& _listener)
g_listener = &_listener;
}
void Listener::notifySuiteStarted(std::string const& _name)
{
if (g_listener)
g_listener->suiteStarted(_name);
}
void Listener::notifyTestStarted(std::string const& _name)
{
if (g_listener)

4
test/TestHelper.h

@ -164,7 +164,7 @@ public:
bool vmtrace = false; ///< Create EVM execution tracer // TODO: Link with log verbosity?
bool fillTests = false; ///< Create JSON test files from execution results
bool stats = false; ///< Execution time stats
bool statsFull = false; ///< Output full stats - execution times for every test
std::string statsOutFile; ///< Stats output file. "out" for standard output
/// Test selection
/// @{
@ -191,10 +191,12 @@ class Listener
public:
virtual ~Listener() = default;
virtual void suiteStarted(std::string const&) {}
virtual void testStarted(std::string const& _name) = 0;
virtual void testFinished() = 0;
static void registerListener(Listener& _listener);
static void notifySuiteStarted(std::string const& _name);
static void notifyTestStarted(std::string const& _name);
static void notifyTestFinished();

5
test/state.cpp

@ -31,7 +31,6 @@
#include <libethereum/Defaults.h>
#include <libevm/VM.h>
#include "TestHelper.h"
#include "Stats.h"
using namespace std;
using namespace json_spirit;
@ -42,9 +41,6 @@ namespace dev { namespace test {
void doStateTests(json_spirit::mValue& v, bool _fillin)
{
if (Options::get().stats)
Listener::registerListener(Stats::get());
for (auto& i: v.get_obj())
{
std::cout << " " << i.first << "\n";
@ -254,6 +250,7 @@ BOOST_AUTO_TEST_CASE(stRandom)
string s = asString(dev::contents(path.string()));
BOOST_REQUIRE_MESSAGE(s.length() > 0, "Content of " + path.string() + " is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?");
json_spirit::read_string(s, v);
test::Listener::notifySuiteStarted(path.filename().string());
dev::test::doStateTests(v, false);
}
catch (Exception const& _e)

5
test/vm.cpp

@ -25,7 +25,6 @@
#include <libethereum/Executive.h>
#include <libevm/VMFactory.h>
#include "vm.h"
#include "Stats.h"
using namespace std;
using namespace json_spirit;
@ -311,9 +310,6 @@ namespace dev { namespace test {
void doVMTests(json_spirit::mValue& v, bool _fillin)
{
if (Options::get().stats)
Listener::registerListener(Stats::get());
for (auto& i: v.get_obj())
{
std::cout << " " << i.first << "\n";
@ -549,6 +545,7 @@ BOOST_AUTO_TEST_CASE(vmRandom)
string s = asString(dev::contents(path.string()));
BOOST_REQUIRE_MESSAGE(s.length() > 0, "Content of " + path.string() + " is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?");
json_spirit::read_string(s, v);
test::Listener::notifySuiteStarted(path.filename().string());
doVMTests(v, false);
}
catch (Exception const& _e)

Loading…
Cancel
Save