mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Allow access to functions in inline assembly.
This commit is contained in:
parent
1ab0f25dff
commit
67ca3bb4b9
@ -452,8 +452,8 @@ Note that the order of arguments is reversed in functional-style as opposed to t
|
|||||||
way. If you use functional-style, the first argument will end up on the stack top.
|
way. If you use functional-style, the first argument will end up on the stack top.
|
||||||
|
|
||||||
|
|
||||||
Access to External Variables
|
Access to External Variables and Functions
|
||||||
----------------------------
|
------------------------------------------
|
||||||
|
|
||||||
Solidity variables and other identifiers can be accessed by simply using their name.
|
Solidity variables and other identifiers can be accessed by simply using their name.
|
||||||
For storage and memory variables, this will push the address and not the value onto the
|
For storage and memory variables, this will push the address and not the value onto the
|
||||||
@ -461,6 +461,17 @@ stack. Also note that non-struct and non-array storage variable addresses occupy
|
|||||||
on the stack: One for the address and one for the byte offset inside the storage slot.
|
on the stack: One for the address and one for the byte offset inside the storage slot.
|
||||||
In assignments (see below), we can even use local Solidity variables to assign to.
|
In assignments (see below), we can even use local Solidity variables to assign to.
|
||||||
|
|
||||||
|
Functions external to inline assembly can also be accessed: The assemble will
|
||||||
|
push their entry label (with virtual function resolution applied). The calling semantics
|
||||||
|
in solidity are:
|
||||||
|
|
||||||
|
- the caller pushes return label, arg1, arg2, ..., argn
|
||||||
|
- the call returns with ret1, ret2, ..., retn
|
||||||
|
|
||||||
|
This feature is still a bit cumbersome to use, because the stack offset essentially
|
||||||
|
changes during the call, and thus references to local variables will be wrong.
|
||||||
|
It is planned that the stack height changes can be specified in inline assembly.
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
contract c {
|
contract c {
|
||||||
|
@ -502,8 +502,9 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
{
|
{
|
||||||
ErrorList errors;
|
ErrorList errors;
|
||||||
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors);
|
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors);
|
||||||
int startStackHeight = m_context.stackHeight();
|
unsigned startStackHeight = m_context.stackHeight();
|
||||||
m_context.appendInlineAssembly(codeGen.assemble(
|
codeGen.assemble(
|
||||||
|
m_context.nonConstAssembly(),
|
||||||
[&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
|
[&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
|
||||||
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
|
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
|
||||||
if (ref == _inlineAssembly.annotation().externalReferences.end())
|
if (ref == _inlineAssembly.annotation().externalReferences.end())
|
||||||
@ -513,19 +514,14 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
|
if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
|
||||||
{
|
{
|
||||||
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
|
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
|
||||||
if (/*FunctionDefinition const* functionDef = */dynamic_cast<FunctionDefinition const*>(decl))
|
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
|
||||||
{
|
_assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag());
|
||||||
solAssert(false, "Referencing local functions in inline assembly not yet implemented.");
|
|
||||||
// This does not work directly, because the label does not exist in _assembly
|
|
||||||
// (it is a fresh assembly object).
|
|
||||||
// _assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag());
|
|
||||||
}
|
|
||||||
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
|
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
|
||||||
{
|
{
|
||||||
solAssert(!variable->isConstant(), "");
|
solAssert(!variable->isConstant(), "");
|
||||||
if (m_context.isLocalVariable(variable))
|
if (m_context.isLocalVariable(variable))
|
||||||
{
|
{
|
||||||
int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable);
|
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
|
||||||
if (stackDiff < 1 || stackDiff > 16)
|
if (stackDiff < 1 || stackDiff > 16)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
CompilerError() <<
|
||||||
@ -565,7 +561,7 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
"Can only assign to stack variables in inline assembly."
|
"Can only assign to stack variables in inline assembly."
|
||||||
);
|
);
|
||||||
unsigned size = variable->type()->sizeOnStack();
|
unsigned size = variable->type()->sizeOnStack();
|
||||||
int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable) - size;
|
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size;
|
||||||
if (stackDiff > 16 || stackDiff < 1)
|
if (stackDiff > 16 || stackDiff < 1)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
CompilerError() <<
|
||||||
@ -578,8 +574,9 @@ bool Compiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
));
|
);
|
||||||
solAssert(errors.empty(), "Code generation for inline assembly with errors requested.");
|
solAssert(errors.empty(), "Code generation for inline assembly with errors requested.");
|
||||||
|
m_context.setStackOffset(startStackHeight);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +113,6 @@ public:
|
|||||||
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
|
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
|
||||||
/// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset.
|
/// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset.
|
||||||
eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); }
|
eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); }
|
||||||
/// Appends the given code (used by inline assembly) ignoring any stack height changes.
|
|
||||||
void appendInlineAssembly(eth::Assembly const& _assembly) { int deposit = m_asm.deposit(); m_asm.append(_assembly); m_asm.setDeposit(deposit); }
|
|
||||||
/// Pushes the size of the final program
|
/// Pushes the size of the final program
|
||||||
void appendProgramSize() { return m_asm.appendProgramSize(); }
|
void appendProgramSize() { return m_asm.appendProgramSize(); }
|
||||||
/// Adds data to the data section, pushes a reference to the stack
|
/// Adds data to the data section, pushes a reference to the stack
|
||||||
@ -140,6 +138,10 @@ public:
|
|||||||
void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); }
|
void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); }
|
||||||
|
|
||||||
eth::Assembly const& assembly() const { return m_asm; }
|
eth::Assembly const& assembly() const { return m_asm; }
|
||||||
|
/// @returns non-const reference to the underlying assembly. Should be avoided in favour of
|
||||||
|
/// wrappers in this class.
|
||||||
|
eth::Assembly& nonConstAssembly() { return m_asm; }
|
||||||
|
|
||||||
/// @arg _sourceCodes is the map of input files to source code strings
|
/// @arg _sourceCodes is the map of input files to source code strings
|
||||||
/// @arg _inJsonFormat shows whether the out should be in Json format
|
/// @arg _inJsonFormat shows whether the out should be in Json format
|
||||||
Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const
|
Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const
|
||||||
|
@ -36,7 +36,8 @@ using namespace dev::solidity::assembly;
|
|||||||
|
|
||||||
struct GeneratorState
|
struct GeneratorState
|
||||||
{
|
{
|
||||||
explicit GeneratorState(ErrorList& _errors): errors(_errors) {}
|
GeneratorState(ErrorList& _errors, eth::Assembly& _assembly):
|
||||||
|
errors(_errors), assembly(_assembly) {}
|
||||||
|
|
||||||
void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation())
|
void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation())
|
||||||
{
|
{
|
||||||
@ -66,10 +67,10 @@ struct GeneratorState
|
|||||||
return label != labels.end() ? &label->second : nullptr;
|
return label != labels.end() ? &label->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
eth::Assembly assembly;
|
|
||||||
map<string, eth::AssemblyItem> labels;
|
map<string, eth::AssemblyItem> labels;
|
||||||
vector<pair<string, int>> variables; ///< name plus stack height
|
vector<pair<string, int>> variables; ///< name plus stack height
|
||||||
ErrorList& errors;
|
ErrorList& errors;
|
||||||
|
eth::Assembly& assembly;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,7 +268,8 @@ private:
|
|||||||
bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
|
bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
|
||||||
{
|
{
|
||||||
size_t initialErrorLen = m_errors.size();
|
size_t initialErrorLen = m_errors.size();
|
||||||
GeneratorState state(m_errors);
|
eth::Assembly assembly;
|
||||||
|
GeneratorState state(m_errors, assembly);
|
||||||
(LabelOrganizer(state))(m_parsedData);
|
(LabelOrganizer(state))(m_parsedData);
|
||||||
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
||||||
return m_errors.size() == initialErrorLen;
|
return m_errors.size() == initialErrorLen;
|
||||||
@ -275,9 +277,17 @@ bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAcces
|
|||||||
|
|
||||||
eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
|
eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
|
||||||
{
|
{
|
||||||
GeneratorState state(m_errors);
|
eth::Assembly assembly;
|
||||||
|
GeneratorState state(m_errors, assembly);
|
||||||
|
(LabelOrganizer(state))(m_parsedData);
|
||||||
|
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
||||||
|
return assembly;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
|
||||||
|
{
|
||||||
|
GeneratorState state(m_errors, _assembly);
|
||||||
(LabelOrganizer(state))(m_parsedData);
|
(LabelOrganizer(state))(m_parsedData);
|
||||||
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
(CodeTransform(state, _identifierAccess))(m_parsedData);
|
||||||
return state.assembly;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,8 @@ public:
|
|||||||
bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess());
|
bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess());
|
||||||
/// Performs code generation and @returns the result.
|
/// Performs code generation and @returns the result.
|
||||||
eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess());
|
eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess());
|
||||||
|
/// Performs code generation and appends generated to to _assembly.
|
||||||
|
void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess());
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Block const& m_parsedData;
|
Block const& m_parsedData;
|
||||||
|
@ -6595,6 +6595,25 @@ BOOST_AUTO_TEST_CASE(inline_assembly_jumps)
|
|||||||
BOOST_CHECK(callContractFunction("f()", u256(7)) == encodeArgs(u256(34)));
|
BOOST_CHECK(callContractFunction("f()", u256(7)) == encodeArgs(u256(34)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(inline_assembly_function_access)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract C {
|
||||||
|
uint public x;
|
||||||
|
function g(uint y) { x = 2 * y; assembly { stop } }
|
||||||
|
function f(uint _x) {
|
||||||
|
assembly {
|
||||||
|
_x
|
||||||
|
jump(g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
BOOST_CHECK(callContractFunction("f(uint256)", u256(5)) == encodeArgs());
|
||||||
|
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(10)));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(index_access_with_type_conversion)
|
BOOST_AUTO_TEST_CASE(index_access_with_type_conversion)
|
||||||
{
|
{
|
||||||
// Test for a bug where higher order bits cleanup was not done for array index access.
|
// Test for a bug where higher order bits cleanup was not done for array index access.
|
||||||
|
Loading…
Reference in New Issue
Block a user