Merge pull request #9371 from ethereum/refactorInterpreter

Refactor yul interpreter.
This commit is contained in:
chriseth 2020-07-13 18:27:47 +02:00 committed by GitHub
commit 289fc7a9d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 88 deletions

View File

@ -99,11 +99,9 @@ string EwasmTranslationTest::interpret()
InterpreterState state; InterpreterState state;
state.maxTraceSize = 10000; state.maxTraceSize = 10000;
state.maxSteps = 100000; state.maxSteps = 100000;
WasmDialect dialect;
Interpreter interpreter(state, dialect);
try try
{ {
interpreter(*m_object->code); Interpreter::run(state, WasmDialect{}, *m_object->code);
} }
catch (InterpreterTerminatedGeneric const&) catch (InterpreterTerminatedGeneric const&)
{ {

View File

@ -88,10 +88,9 @@ string YulInterpreterTest::interpret()
InterpreterState state; InterpreterState state;
state.maxTraceSize = 10000; state.maxTraceSize = 10000;
state.maxSteps = 10000; state.maxSteps = 10000;
Interpreter interpreter(state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}));
try try
{ {
interpreter(*m_ast); Interpreter::run(state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), *m_ast);
} }
catch (InterpreterTerminatedGeneric const&) catch (InterpreterTerminatedGeneric const&)
{ {

View File

@ -44,12 +44,11 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret(
0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd, 0xc7, 0x60, 0x5f, 0x7c, 0xcd, 0xfb, 0x92, 0xcd,
0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde 0x8e, 0xf3, 0x9b, 0xe4, 0x4f, 0x6c, 0x14, 0xde
}; };
Interpreter interpreter(state, _dialect);
TerminationReason reason = TerminationReason::None; TerminationReason reason = TerminationReason::None;
try try
{ {
interpreter(*_ast); Interpreter::run(state, _dialect, *_ast);
} }
catch (StepLimitReached const&) catch (StepLimitReached const&)
{ {

View File

@ -121,11 +121,12 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector<u256> const& _a
for (u256 const& a: _arguments) for (u256 const& a: _arguments)
arg.emplace_back(uint64_t(a & uint64_t(-1))); arg.emplace_back(uint64_t(a & uint64_t(-1)));
if (_fun == "datasize"_yulstring) string fun = _fun.str();
if (fun == "datasize")
return u256(keccak256(h256(_arguments.at(0)))) & 0xfff; return u256(keccak256(h256(_arguments.at(0)))) & 0xfff;
else if (_fun == "dataoffset"_yulstring) else if (fun == "dataoffset")
return u256(keccak256(h256(_arguments.at(0) + 2))) & 0xfff; return u256(keccak256(h256(_arguments.at(0) + 2))) & 0xfff;
else if (_fun == "datacopy"_yulstring) else if (fun == "datacopy")
{ {
// This is identical to codecopy. // This is identical to codecopy.
if (accessMemory(_arguments.at(0), _arguments.at(2))) if (accessMemory(_arguments.at(0), _arguments.at(2)))
@ -138,42 +139,42 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector<u256> const& _a
); );
return 0; return 0;
} }
else if (_fun == "i32.drop"_yulstring || _fun == "i64.drop"_yulstring || _fun == "nop"_yulstring) else if (fun == "i32.drop" || fun == "i64.drop" || fun == "nop")
return {}; return {};
else if (_fun == "i32.wrap_i64"_yulstring) else if (fun == "i32.wrap_i64")
return arg.at(0) & uint32_t(-1); return arg.at(0) & uint32_t(-1);
else if (_fun == "i64.extend_i32_u"_yulstring) else if (fun == "i64.extend_i32_u")
// Return the same as above because everything is u256 anyway. // Return the same as above because everything is u256 anyway.
return arg.at(0) & uint32_t(-1); return arg.at(0) & uint32_t(-1);
else if (_fun == "unreachable"_yulstring) else if (fun == "unreachable")
{ {
logTrace(evmasm::Instruction::INVALID, {}); logTrace(evmasm::Instruction::INVALID, {});
throw ExplicitlyTerminated(); throw ExplicitlyTerminated();
} }
else if (_fun == "i64.store"_yulstring) else if (fun == "i64.store")
{ {
accessMemory(arg[0], 8); accessMemory(arg[0], 8);
writeMemoryWord(arg[0], arg[1]); writeMemoryWord(arg[0], arg[1]);
return 0; return 0;
} }
else if (_fun == "i64.store8"_yulstring || _fun == "i32.store8"_yulstring) else if (fun == "i64.store8" || fun == "i32.store8")
{ {
accessMemory(arg[0], 1); accessMemory(arg[0], 1);
writeMemoryByte(arg[0], static_cast<uint8_t>(arg[1] & 0xff)); writeMemoryByte(arg[0], static_cast<uint8_t>(arg[1] & 0xff));
return 0; return 0;
} }
else if (_fun == "i64.load"_yulstring) else if (fun == "i64.load")
{ {
accessMemory(arg[0], 8); accessMemory(arg[0], 8);
return readMemoryWord(arg[0]); return readMemoryWord(arg[0]);
} }
else if (_fun == "i32.store"_yulstring) else if (fun == "i32.store")
{ {
accessMemory(arg[0], 4); accessMemory(arg[0], 4);
writeMemoryHalfWord(arg[0], arg[1]); writeMemoryHalfWord(arg[0], arg[1]);
return 0; return 0;
} }
else if (_fun == "i32.load"_yulstring) else if (fun == "i32.load")
{ {
accessMemory(arg[0], 4); accessMemory(arg[0], 4);
return readMemoryHalfWord(arg[0]); return readMemoryHalfWord(arg[0]);
@ -188,7 +189,7 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector<u256> const& _a
else if (_fun == "i64.ctz"_yulstring) else if (_fun == "i64.ctz"_yulstring)
return ctz64(arg[0]); return ctz64(arg[0]);
string prefix = _fun.str(); string prefix = fun;
string suffix; string suffix;
auto dot = prefix.find("."); auto dot = prefix.find(".");
if (dot != string::npos) if (dot != string::npos)

View File

@ -64,6 +64,12 @@ void InterpreterState::dumpTraceAndState(ostream& _out) const
_out << " " << slot.first.hex() << ": " << slot.second.hex() << endl; _out << " " << slot.first.hex() << ": " << slot.second.hex() << endl;
} }
void Interpreter::run(InterpreterState& _state, Dialect const& _dialect, Block const& _ast)
{
Scope scope;
Interpreter{_state, _dialect, scope}(_ast);
}
void Interpreter::operator()(ExpressionStatement const& _expressionStatement) void Interpreter::operator()(ExpressionStatement const& _expressionStatement)
{ {
evaluateMulti(_expressionStatement.expression); evaluateMulti(_expressionStatement.expression);
@ -94,8 +100,7 @@ void Interpreter::operator()(VariableDeclaration const& _declaration)
YulString varName = _declaration.variables.at(i).name; YulString varName = _declaration.variables.at(i).name;
solAssert(!m_variables.count(varName), ""); solAssert(!m_variables.count(varName), "");
m_variables[varName] = values.at(i); m_variables[varName] = values.at(i);
solAssert(!m_scopes.back().count(varName), ""); m_scope->names.emplace(varName, nullptr);
m_scopes.back().emplace(varName, nullptr);
} }
} }
@ -128,8 +133,8 @@ void Interpreter::operator()(ForLoop const& _forLoop)
{ {
solAssert(_forLoop.condition, ""); solAssert(_forLoop.condition, "");
openScope(); enterScope(_forLoop.pre);
ScopeGuard g([this]{ closeScope(); }); ScopeGuard g([this]{ leaveScope(); });
for (auto const& statement: _forLoop.pre.statements) for (auto const& statement: _forLoop.pre.statements)
{ {
@ -176,14 +181,13 @@ void Interpreter::operator()(Block const& _block)
m_state.trace.emplace_back("Interpreter execution step limit reached."); m_state.trace.emplace_back("Interpreter execution step limit reached.");
throw StepLimitReached(); throw StepLimitReached();
} }
openScope(); enterScope(_block);
// Register functions. // Register functions.
for (auto const& statement: _block.statements) for (auto const& statement: _block.statements)
if (holds_alternative<FunctionDefinition>(statement)) if (holds_alternative<FunctionDefinition>(statement))
{ {
FunctionDefinition const& funDef = std::get<FunctionDefinition>(statement); FunctionDefinition const& funDef = std::get<FunctionDefinition>(statement);
solAssert(!m_scopes.back().count(funDef.name), ""); m_scope->names.emplace(funDef.name, &funDef);
m_scopes.back().emplace(funDef.name, &funDef);
} }
for (auto const& statement: _block.statements) for (auto const& statement: _block.statements)
@ -193,29 +197,41 @@ void Interpreter::operator()(Block const& _block)
break; break;
} }
closeScope(); leaveScope();
} }
u256 Interpreter::evaluate(Expression const& _expression) u256 Interpreter::evaluate(Expression const& _expression)
{ {
ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_scopes); ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables);
ev.visit(_expression); ev.visit(_expression);
return ev.value(); return ev.value();
} }
vector<u256> Interpreter::evaluateMulti(Expression const& _expression) vector<u256> Interpreter::evaluateMulti(Expression const& _expression)
{ {
ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_scopes); ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables);
ev.visit(_expression); ev.visit(_expression);
return ev.values(); return ev.values();
} }
void Interpreter::closeScope() void Interpreter::enterScope(Block const& _block)
{ {
for (auto const& [var, funDeclaration]: m_scopes.back()) if (!m_scope->subScopes.count(&_block))
m_scope->subScopes[&_block] = make_unique<Scope>(Scope{
{},
{},
m_scope
});
m_scope = m_scope->subScopes[&_block].get();
}
void Interpreter::leaveScope()
{
for (auto const& [var, funDeclaration]: m_scope->names)
if (!funDeclaration) if (!funDeclaration)
solAssert(m_variables.erase(var) == 1, ""); m_variables.erase(var);
m_scopes.pop_back(); m_scope = m_scope->parent;
yulAssert(m_scope, "");
} }
void ExpressionEvaluator::operator()(Literal const& _literal) void ExpressionEvaluator::operator()(Literal const& _literal)
@ -253,10 +269,15 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
return; return;
} }
auto [functionScopes, fun] = findFunctionAndScope(_funCall.functionName.name); Scope* scope = &m_scope;
for (; scope; scope = scope->parent)
if (scope->names.count(_funCall.functionName.name))
break;
yulAssert(scope, "");
solAssert(fun, "Function not found."); FunctionDefinition const* fun = scope->names.at(_funCall.functionName.name);
solAssert(m_values.size() == fun->parameters.size(), ""); yulAssert(fun, "Function not found.");
yulAssert(m_values.size() == fun->parameters.size(), "");
map<YulString, u256> variables; map<YulString, u256> variables;
for (size_t i = 0; i < fun->parameters.size(); ++i) for (size_t i = 0; i < fun->parameters.size(); ++i)
variables[fun->parameters.at(i).name] = m_values.at(i); variables[fun->parameters.at(i).name] = m_values.at(i);
@ -264,7 +285,7 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
variables[fun->returnVariables.at(i).name] = 0; variables[fun->returnVariables.at(i).name] = 0;
m_state.controlFlowState = ControlFlowState::Default; m_state.controlFlowState = ControlFlowState::Default;
Interpreter interpreter(m_state, m_dialect, variables, functionScopes); Interpreter interpreter(m_state, m_dialect, *scope, std::move(variables));
interpreter(fun->body); interpreter(fun->body);
m_state.controlFlowState = ControlFlowState::Default; m_state.controlFlowState = ControlFlowState::Default;
@ -297,27 +318,3 @@ void ExpressionEvaluator::evaluateArgs(vector<Expression> const& _expr)
m_values = std::move(values); m_values = std::move(values);
std::reverse(m_values.begin(), m_values.end()); std::reverse(m_values.begin(), m_values.end());
} }
pair<
vector<map<YulString, FunctionDefinition const*>>,
FunctionDefinition const*
> ExpressionEvaluator::findFunctionAndScope(YulString _functionName) const
{
FunctionDefinition const* fun = nullptr;
std::vector<std::map<YulString, FunctionDefinition const*>> newScopes;
for (auto const& scope: m_scopes)
{
// Copy over all functions.
newScopes.emplace_back();
for (auto const& [name, funDef]: scope)
if (funDef)
newScopes.back().emplace(name, funDef);
// Stop at the called function.
if (scope.count(_functionName))
{
fun = scope.at(_functionName);
break;
}
}
return {move(newScopes), fun};
}

View File

@ -96,23 +96,37 @@ struct InterpreterState
void dumpTraceAndState(std::ostream& _out) const; void dumpTraceAndState(std::ostream& _out) const;
}; };
/**
* Scope structure built and maintained during execution.
*/
struct Scope
{
/// Used for variables and functions. Value is nullptr for variables.
std::map<YulString, FunctionDefinition const*> names;
std::map<Block const*, std::unique_ptr<Scope>> subScopes;
Scope* parent = nullptr;
};
/** /**
* Yul interpreter. * Yul interpreter.
*/ */
class Interpreter: public ASTWalker class Interpreter: public ASTWalker
{ {
public: public:
static void run(InterpreterState& _state, Dialect const& _dialect, Block const& _ast);
Interpreter( Interpreter(
InterpreterState& _state, InterpreterState& _state,
Dialect const& _dialect, Dialect const& _dialect,
std::map<YulString, u256> _variables = {}, Scope& _scope,
std::vector<std::map<YulString, FunctionDefinition const*>> _scopes = {} std::map<YulString, u256> _variables = {}
): ):
m_dialect(_dialect), m_dialect(_dialect),
m_state(_state), m_state(_state),
m_variables(std::move(_variables)), m_variables(std::move(_variables)),
m_scopes(std::move(_scopes)) m_scope(&_scope)
{} {
}
void operator()(ExpressionStatement const& _statement) override; void operator()(ExpressionStatement const& _statement) override;
void operator()(Assignment const& _assignment) override; void operator()(Assignment const& _assignment) override;
@ -136,18 +150,14 @@ private:
/// Evaluates the expression and returns its value. /// Evaluates the expression and returns its value.
std::vector<u256> evaluateMulti(Expression const& _expression); std::vector<u256> evaluateMulti(Expression const& _expression);
void openScope() { m_scopes.emplace_back(); } void enterScope(Block const& _block);
/// Unregisters variables and functions. void leaveScope();
void closeScope();
Dialect const& m_dialect; Dialect const& m_dialect;
InterpreterState& m_state; InterpreterState& m_state;
/// Values of variables. /// Values of variables.
std::map<YulString, u256> m_variables; std::map<YulString, u256> m_variables;
/// Scopes of variables and functions. Used for lookup, clearing at end of blocks Scope* m_scope;
/// and passing over the visible functions across function calls.
/// The pointer is nullptr if and only if the key is a variable.
std::vector<std::map<YulString, FunctionDefinition const*>> m_scopes;
}; };
/** /**
@ -159,13 +169,13 @@ public:
ExpressionEvaluator( ExpressionEvaluator(
InterpreterState& _state, InterpreterState& _state,
Dialect const& _dialect, Dialect const& _dialect,
std::map<YulString, u256> const& _variables, Scope& _scope,
std::vector<std::map<YulString, FunctionDefinition const*>> const& _scopes std::map<YulString, u256> const& _variables
): ):
m_state(_state), m_state(_state),
m_dialect(_dialect), m_dialect(_dialect),
m_variables(_variables), m_variables(_variables),
m_scopes(_scopes) m_scope(_scope)
{} {}
void operator()(Literal const&) override; void operator()(Literal const&) override;
@ -184,19 +194,11 @@ private:
/// stores it in m_value. /// stores it in m_value.
void evaluateArgs(std::vector<Expression> const& _expr); void evaluateArgs(std::vector<Expression> const& _expr);
/// Finds the function called @a _functionName in the current scope stack and returns
/// the function's scope stack (with variables removed) and definition.
std::pair<
std::vector<std::map<YulString, FunctionDefinition const*>>,
FunctionDefinition const*
> findFunctionAndScope(YulString _functionName) const;
InterpreterState& m_state; InterpreterState& m_state;
Dialect const& m_dialect; Dialect const& m_dialect;
/// Values of variables. /// Values of variables.
std::map<YulString, u256> const& m_variables; std::map<YulString, u256> const& m_variables;
/// Stack of scopes in the current context. Scope& m_scope;
std::vector<std::map<YulString, FunctionDefinition const*>> const& m_scopes;
/// Current value of the expression /// Current value of the expression
std::vector<u256> m_values; std::vector<u256> m_values;
}; };

View File

@ -88,11 +88,10 @@ void interpret(string const& _source)
InterpreterState state; InterpreterState state;
state.maxTraceSize = 10000; state.maxTraceSize = 10000;
Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}));
Interpreter interpreter(state, dialect);
try try
{ {
interpreter(*ast); Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}));
Interpreter::run(state, dialect, *ast);
} }
catch (InterpreterTerminatedGeneric const&) catch (InterpreterTerminatedGeneric const&)
{ {