[050] Reserving and popping local vars in their scope

This commit is contained in:
Leonardo Alt 2018-05-03 16:40:33 +02:00
parent 0e9415bc31
commit 1f77deada1
5 changed files with 111 additions and 41 deletions

View File

@ -50,6 +50,7 @@ Compiler Features:
* C API (``libsolc``): Export the ``solidity_license``, ``solidity_version`` and ``solidity_compile`` methods. * C API (``libsolc``): Export the ``solidity_license``, ``solidity_version`` and ``solidity_compile`` methods.
* Type Checker: Show named argument in case of error. * Type Checker: Show named argument in case of error.
* Tests: Determine transaction status during IPC calls. * Tests: Determine transaction status during IPC calls.
* Code Generator: Allocate and free local variables according to their scope.
Bugfixes: Bugfixes:
* Tests: Fix chain parameters to make ipc tests work with newer versions of cpp-ethereum. * Tests: Fix chain parameters to make ipc tests work with newer versions of cpp-ethereum.

View File

@ -130,7 +130,7 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration,
m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent); m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent);
} }
void CompilerContext::removeVariable(VariableDeclaration const& _declaration) void CompilerContext::removeVariable(Declaration const& _declaration)
{ {
solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), ""); solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), "");
m_localVariables[&_declaration].pop_back(); m_localVariables[&_declaration].pop_back();
@ -138,6 +138,19 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
m_localVariables.erase(&_declaration); m_localVariables.erase(&_declaration);
} }
void CompilerContext::removeVariablesAboveStackHeight(unsigned _stackHeight)
{
vector<Declaration const*> toRemove;
for (auto _var: m_localVariables)
{
solAssert(!_var.second.empty(), "");
if (_var.second.back() >= _stackHeight)
toRemove.push_back(_var.first);
}
for (auto _var: toRemove)
removeVariable(*_var);
}
eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const
{ {
auto ret = m_compiledContracts.find(&_contract); auto ret = m_compiledContracts.find(&_contract);

View File

@ -71,7 +71,9 @@ public:
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(VariableDeclaration const& _declaration); void removeVariable(Declaration const& _declaration);
/// Removes all local variables currently allocated above _stackHeight.
void removeVariablesAboveStackHeight(unsigned _stackHeight);
void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; } void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; }
eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; eth::Assembly const& compiledContract(ContractDefinition const& _contract) const;

View File

@ -427,7 +427,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context.startFunction(_function); m_context.startFunction(_function);
// stack upon entry: [return address] [arg0] [arg1] ... [argn] // stack upon entry: [return address] [arg0] [arg1] ... [argn]
// reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] // reserve additional slots: [retarg0] ... [retargm]
unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters()); unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters());
if (!_function.isConstructor()) if (!_function.isConstructor())
@ -441,22 +441,18 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters()) for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters())
appendStackVariableInitialisation(*variable); appendStackVariableInitialisation(*variable);
for (VariableDeclaration const* localVariable: _function.localVariables())
appendStackVariableInitialisation(*localVariable);
if (_function.isConstructor()) if (_function.isConstructor())
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope()))) if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
appendBaseConstructor(*c); appendBaseConstructor(*c);
solAssert(m_returnTags.empty(), "");
m_breakTags.clear(); m_breakTags.clear();
m_continueTags.clear(); m_continueTags.clear();
m_stackCleanupForReturn = 0;
m_currentFunction = &_function; m_currentFunction = &_function;
m_modifierDepth = -1; m_modifierDepth = -1;
m_scopeStackHeight.clear();
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();
solAssert(m_returnTags.empty(), ""); solAssert(m_returnTags.empty(), "");
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout // Now we need to re-shuffle the stack. For this we keep a record of the stack layout
@ -467,14 +463,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters()); unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters());
unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters()); unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters());
unsigned const c_localVariablesSize = CompilerUtils::sizeOnStack(_function.localVariables());
vector<int> stackLayout; vector<int> stackLayout;
stackLayout.push_back(c_returnValuesSize); // target of return address stackLayout.push_back(c_returnValuesSize); // target of return address
stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments
for (unsigned i = 0; i < c_returnValuesSize; ++i) for (unsigned i = 0; i < c_returnValuesSize; ++i)
stackLayout.push_back(i); stackLayout.push_back(i);
stackLayout += vector<int>(c_localVariablesSize, -1);
if (stackLayout.size() > 17) if (stackLayout.size() > 17)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
@ -493,18 +487,19 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context << swapInstruction(stackLayout.size() - stackLayout.back() - 1); m_context << swapInstruction(stackLayout.size() - stackLayout.back() - 1);
swap(stackLayout[stackLayout.back()], stackLayout.back()); swap(stackLayout[stackLayout.back()], stackLayout.back());
} }
//@todo assert that everything is in place now for (int i = 0; i < int(stackLayout.size()); ++i)
if (stackLayout[i] != i)
solAssert(false, "Invalid stack layout on cleanup.");
for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters()) for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters())
m_context.removeVariable(*variable); m_context.removeVariable(*variable);
for (VariableDeclaration const* localVariable: _function.localVariables())
m_context.removeVariable(*localVariable);
m_context.adjustStackOffset(-(int)c_returnValuesSize); m_context.adjustStackOffset(-(int)c_returnValuesSize);
/// The constructor and the fallback function doesn't to jump out. /// The constructor and the fallback function doesn't to jump out.
if (!_function.isConstructor() && !_function.isFallback()) if (!_function.isConstructor() && !_function.isFallback())
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
return false; return false;
} }
@ -669,14 +664,16 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement)
eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag();
m_breakTags.push_back(loopEnd); m_breakTags.push_back({loopEnd, m_context.stackHeight()});
visitLoop(&_whileStatement);
m_context << loopStart; m_context << loopStart;
if (_whileStatement.isDoWhile()) if (_whileStatement.isDoWhile())
{ {
eth::AssemblyItem condition = m_context.newTag(); eth::AssemblyItem condition = m_context.newTag();
m_continueTags.push_back(condition); m_continueTags.push_back({condition, m_context.stackHeight()});
_whileStatement.body().accept(*this); _whileStatement.body().accept(*this);
@ -687,7 +684,7 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement)
} }
else else
{ {
m_continueTags.push_back(loopStart); m_continueTags.push_back({loopStart, m_context.stackHeight()});
compileExpression(_whileStatement.condition()); compileExpression(_whileStatement.condition());
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(loopEnd); m_context.appendConditionalJumpTo(loopEnd);
@ -712,12 +709,14 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag();
eth::AssemblyItem loopNext = m_context.newTag(); eth::AssemblyItem loopNext = m_context.newTag();
m_continueTags.push_back(loopNext);
m_breakTags.push_back(loopEnd); visitLoop(&_forStatement);
if (_forStatement.initializationExpression()) if (_forStatement.initializationExpression())
_forStatement.initializationExpression()->accept(*this); _forStatement.initializationExpression()->accept(*this);
m_breakTags.push_back({loopEnd, m_context.stackHeight()});
m_continueTags.push_back({loopNext, m_context.stackHeight()});
m_context << loopStart; m_context << loopStart;
// if there is no terminating condition in for, default is to always be true // if there is no terminating condition in for, default is to always be true
@ -737,11 +736,16 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
_forStatement.loopExpression()->accept(*this); _forStatement.loopExpression()->accept(*this);
m_context.appendJumpTo(loopStart); m_context.appendJumpTo(loopStart);
m_context << loopEnd; m_context << loopEnd;
m_continueTags.pop_back(); m_continueTags.pop_back();
m_breakTags.pop_back(); m_breakTags.pop_back();
// For the case where no break/return is executed:
// loop initialization variables have to be freed
popScopedVariables(&_forStatement);
checker.check(); checker.check();
return false; return false;
} }
@ -750,7 +754,7 @@ bool ContractCompiler::visit(Continue const& _continueStatement)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); CompilerContext::LocationSetter locationSetter(m_context, _continueStatement);
solAssert(!m_continueTags.empty(), ""); solAssert(!m_continueTags.empty(), "");
m_context.appendJumpTo(m_continueTags.back()); popAndJump(m_context.stackHeight() - m_continueTags.back().second, m_continueTags.back().first);
return false; return false;
} }
@ -758,7 +762,7 @@ bool ContractCompiler::visit(Break const& _breakStatement)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); CompilerContext::LocationSetter locationSetter(m_context, _breakStatement);
solAssert(!m_breakTags.empty(), ""); solAssert(!m_breakTags.empty(), "");
m_context.appendJumpTo(m_breakTags.back()); popAndJump(m_context.stackHeight() - m_breakTags.back().second, m_breakTags.back().first);
return false; return false;
} }
@ -784,10 +788,8 @@ bool ContractCompiler::visit(Return const& _return)
for (auto const& retVariable: boost::adaptors::reverse(returnParameters)) for (auto const& retVariable: boost::adaptors::reverse(returnParameters))
CompilerUtils(m_context).moveToStackVariable(*retVariable); CompilerUtils(m_context).moveToStackVariable(*retVariable);
} }
for (unsigned i = 0; i < m_stackCleanupForReturn; ++i)
m_context << Instruction::POP; popAndJump(m_context.stackHeight() - m_returnTags.back().second, m_returnTags.back().first);
m_context.appendJumpTo(m_returnTags.back());
m_context.adjustStackOffset(m_stackCleanupForReturn);
return false; return false;
} }
@ -810,8 +812,15 @@ bool ContractCompiler::visit(EmitStatement const& _emit)
bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{ {
StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement);
// Local variable slots are reserved when their declaration is visited,
// and freed in the end of their scope.
for (auto _decl: _variableDeclarationStatement.declarations())
if (_decl)
appendStackVariableInitialisation(*_decl);
StackHeightChecker checker(m_context);
if (Expression const* expression = _variableDeclarationStatement.initialValue()) if (Expression const* expression = _variableDeclarationStatement.initialValue())
{ {
CompilerUtils utils(m_context); CompilerUtils utils(m_context);
@ -861,6 +870,18 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
return true; return true;
} }
bool ContractCompiler::visit(Block const& _block)
{
m_scopeStackHeight[m_modifierDepth][&_block] = m_context.stackHeight();
return true;
}
void ContractCompiler::endVisit(Block const& _block)
{
// Frees local variables declared in the scope of this block.
popScopedVariables(&_block);
}
void ContractCompiler::appendMissingFunctions() void ContractCompiler::appendMissingFunctions()
{ {
while (Declaration const* function = m_context.nextFunctionToCompile()) while (Declaration const* function = m_context.nextFunctionToCompile())
@ -916,27 +937,19 @@ void ContractCompiler::appendModifierOrFunctionCode()
modifier.parameters()[i]->annotation().type modifier.parameters()[i]->annotation().type
); );
} }
for (VariableDeclaration const* localVariable: modifier.localVariables())
{
addedVariables.push_back(localVariable);
appendStackVariableInitialisation(*localVariable);
}
stackSurplus = stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters());
CompilerUtils::sizeOnStack(modifier.parameters()) +
CompilerUtils::sizeOnStack(modifier.localVariables());
codeBlock = &modifier.body(); codeBlock = &modifier.body();
} }
} }
if (codeBlock) if (codeBlock)
{ {
m_returnTags.push_back(m_context.newTag()); m_returnTags.push_back({m_context.newTag(), m_context.stackHeight()});
codeBlock->accept(*this); codeBlock->accept(*this);
solAssert(!m_returnTags.empty(), ""); solAssert(!m_returnTags.empty(), "");
m_context << m_returnTags.back(); m_context << m_returnTags.back().first;
m_returnTags.pop_back(); m_returnTags.pop_back();
CompilerUtils(m_context).popStackSlots(stackSurplus); CompilerUtils(m_context).popStackSlots(stackSurplus);
@ -983,3 +996,26 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() const
a << u256(0x20) << u256(0) << Instruction::RETURN; a << u256(0x20) << u256(0) << Instruction::RETURN;
return make_shared<eth::Assembly>(a); return make_shared<eth::Assembly>(a);
} }
void ContractCompiler::popScopedVariables(ASTNode const* _node)
{
unsigned blockHeight = m_scopeStackHeight[m_modifierDepth][_node];
unsigned stackDiff = m_context.stackHeight() - blockHeight;
CompilerUtils(m_context).popStackSlots(stackDiff);
m_context.removeVariablesAboveStackHeight(blockHeight);
m_scopeStackHeight[m_modifierDepth].erase(_node);
if (m_scopeStackHeight[m_modifierDepth].size() == 0)
m_scopeStackHeight.erase(m_modifierDepth);
}
void ContractCompiler::visitLoop(BreakableStatement const* _loop)
{
m_scopeStackHeight[m_modifierDepth][_loop] = m_context.stackHeight();
}
void ContractCompiler::popAndJump(unsigned _amount, eth::AssemblyItem const& _jumpTo)
{
CompilerUtils(m_context).popStackSlots(_amount);
m_context.appendJumpTo(_jumpTo);
m_context.adjustStackOffset(_amount);
}

View File

@ -109,6 +109,8 @@ private:
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
virtual bool visit(ExpressionStatement const& _expressionStatement) override; virtual bool visit(ExpressionStatement const& _expressionStatement) override;
virtual bool visit(PlaceholderStatement const&) override; virtual bool visit(PlaceholderStatement const&) override;
virtual bool visit(Block const& _block) override;
virtual void endVisit(Block const& _block) override;
/// Repeatedly visits all function which are referenced but which are not compiled yet. /// Repeatedly visits all function which are referenced but which are not compiled yet.
void appendMissingFunctions(); void appendMissingFunctions();
@ -123,19 +125,35 @@ private:
/// @returns the runtime assembly for clone contracts. /// @returns the runtime assembly for clone contracts.
eth::AssemblyPointer cloneRuntime() const; eth::AssemblyPointer cloneRuntime() const;
/// Frees the variables of a certain scope (to be used when leaving).
void popScopedVariables(ASTNode const* _node);
/// Pops _amount slots from the stack and jumps to _jumpTo.
/// Also readjusts the stack offset to the original value.
void popAndJump(unsigned _amount, eth::AssemblyItem const& _jumpTo);
/// Sets the stack height for the visited loop.
void visitLoop(BreakableStatement const* _loop);
bool const m_optimise; bool const m_optimise;
/// Pointer to the runtime compiler in case this is a creation compiler. /// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr; ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context; CompilerContext& m_context;
std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement /// Tag to jump to for a "break" statement and the stack height after freeing the local loop variables.
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement std::vector<std::pair<eth::AssemblyItem, unsigned>> m_breakTags;
/// Tag to jump to for a "return" statement, needs to be stacked because of modifiers. /// Tag to jump to for a "continue" statement and the stack height after freeing the local loop variables.
std::vector<eth::AssemblyItem> m_returnTags; std::vector<std::pair<eth::AssemblyItem, unsigned>> m_continueTags;
/// Tag to jump to for a "return" statement and the stack height after freeing the local function or modifier variables.
/// Needs to be stacked because of modifiers.
std::vector<std::pair<eth::AssemblyItem, unsigned>> m_returnTags;
unsigned m_modifierDepth = 0; unsigned m_modifierDepth = 0;
FunctionDefinition const* m_currentFunction = nullptr; FunctionDefinition const* m_currentFunction = nullptr;
unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
// arguments for base constructors, filled in derived-to-base order // arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments; std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments;
/// Stores the variables that were declared inside a specific scope, for each modifier depth.
std::map<unsigned, std::map<ASTNode const*, unsigned>> m_scopeStackHeight;
}; };
} }