mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Support multiple assignment in inline assembly
This commit is contained in:
parent
c0b3e5b078
commit
3b813ed295
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Features:
|
Features:
|
||||||
* Support ``pragma experimental "v0.5.0";`` to turn on upcoming breaking changes.
|
* Support ``pragma experimental "v0.5.0";`` to turn on upcoming breaking changes.
|
||||||
|
* Assembly Parser: Support multiple assignment (``x, y := f()``).
|
||||||
* Code Generator: Added ``.selector`` member on external function types to retrieve their signature.
|
* Code Generator: Added ``.selector`` member on external function types to retrieve their signature.
|
||||||
* Code Generator: Keep a single copy of encoding functions when using the experimental "ABIEncoderV2".
|
* Code Generator: Keep a single copy of encoding functions when using the experimental "ABIEncoderV2".
|
||||||
* Optimizer: Add new optimization step to remove unused ``JUMPDEST``s.
|
* Optimizer: Add new optimization step to remove unused ``JUMPDEST``s.
|
||||||
|
@ -60,16 +60,19 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
|
|||||||
|
|
||||||
void CodeTransform::operator()(Assignment const& _assignment)
|
void CodeTransform::operator()(Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
visitExpression(*_assignment.value);
|
int height = m_assembly.stackHeight();
|
||||||
|
boost::apply_visitor(*this, *_assignment.value);
|
||||||
|
expectDeposit(_assignment.variableNames.size(), height);
|
||||||
|
|
||||||
m_assembly.setSourceLocation(_assignment.location);
|
m_assembly.setSourceLocation(_assignment.location);
|
||||||
generateAssignment(_assignment.variableName);
|
generateAssignment(_assignment.variableNames);
|
||||||
checkStackHeight(&_assignment);
|
checkStackHeight(&_assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeTransform::operator()(StackAssignment const& _assignment)
|
void CodeTransform::operator()(StackAssignment const& _assignment)
|
||||||
{
|
{
|
||||||
m_assembly.setSourceLocation(_assignment.location);
|
m_assembly.setSourceLocation(_assignment.location);
|
||||||
generateAssignment(_assignment.variableName);
|
generateAssignment({_assignment.variableName});
|
||||||
checkStackHeight(&_assignment);
|
checkStackHeight(&_assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,24 +472,27 @@ void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight
|
|||||||
checkStackHeight(&_block);
|
checkStackHeight(&_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeTransform::generateAssignment(Identifier const& _variableName)
|
void CodeTransform::generateAssignment(vector<Identifier> const& _variableNames)
|
||||||
{
|
{
|
||||||
solAssert(m_scope, "");
|
solAssert(m_scope, "");
|
||||||
auto var = m_scope->lookup(_variableName.name);
|
for (auto const& variableName: _variableNames | boost::adaptors::reversed)
|
||||||
if (var)
|
|
||||||
{
|
{
|
||||||
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
|
auto var = m_scope->lookup(variableName.name);
|
||||||
if (int heightDiff = variableHeightDiff(_var, true))
|
if (var)
|
||||||
m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1));
|
{
|
||||||
m_assembly.appendInstruction(solidity::Instruction::POP);
|
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
|
||||||
}
|
if (int heightDiff = variableHeightDiff(_var, true))
|
||||||
else
|
m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1));
|
||||||
{
|
m_assembly.appendInstruction(solidity::Instruction::POP);
|
||||||
solAssert(
|
}
|
||||||
m_identifierAccess.generateCode,
|
else
|
||||||
"Identifier not found and no external access available."
|
{
|
||||||
);
|
solAssert(
|
||||||
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_assembly);
|
m_identifierAccess.generateCode,
|
||||||
|
"Identifier not found and no external access available."
|
||||||
|
);
|
||||||
|
m_identifierAccess.generateCode(variableName, IdentifierContext::LValue, m_assembly);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ private:
|
|||||||
/// to @a _blackStartStackHeight.
|
/// to @a _blackStartStackHeight.
|
||||||
void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
|
void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
|
||||||
|
|
||||||
void generateAssignment(solidity::assembly::Identifier const& _variableName);
|
void generateAssignment(std::vector<solidity::assembly::Identifier> const& _variableNames);
|
||||||
|
|
||||||
/// Determines the stack height difference to the given variables. Throws
|
/// Determines the stack height difference to the given variables. Throws
|
||||||
/// if it is not yet in scope or the height difference is too large. Returns
|
/// if it is not yet in scope or the height difference is too large. Returns
|
||||||
|
@ -163,11 +163,25 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
|
|||||||
|
|
||||||
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
|
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
|
int const expectedItems = _assignment.variableNames.size();
|
||||||
|
solAssert(expectedItems >= 1, "");
|
||||||
int const stackHeight = m_stackHeight;
|
int const stackHeight = m_stackHeight;
|
||||||
bool success = boost::apply_visitor(*this, *_assignment.value);
|
bool success = boost::apply_visitor(*this, *_assignment.value);
|
||||||
solAssert(m_stackHeight >= stackHeight, "Negative value size.");
|
if ((m_stackHeight - stackHeight) != expectedItems)
|
||||||
if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight))
|
{
|
||||||
success = false;
|
m_errorReporter.declarationError(
|
||||||
|
_assignment.location,
|
||||||
|
"Variable count does not match number of values (" +
|
||||||
|
to_string(expectedItems) +
|
||||||
|
" vs. " +
|
||||||
|
to_string(m_stackHeight - stackHeight) +
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto const& variableName: _assignment.variableNames)
|
||||||
|
if (!checkAssignment(variableName, 1))
|
||||||
|
success = false;
|
||||||
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
|
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,11 @@ struct Label { SourceLocation location; std::string name; };
|
|||||||
struct StackAssignment { SourceLocation location; Identifier variableName; };
|
struct StackAssignment { SourceLocation location; Identifier variableName; };
|
||||||
/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand
|
/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand
|
||||||
/// side and requires x to occupy exactly one stack slot.
|
/// side and requires x to occupy exactly one stack slot.
|
||||||
struct Assignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; };
|
///
|
||||||
|
/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy
|
||||||
|
/// a single stack slot and expects a single expression on the right hand returning
|
||||||
|
/// the same amount of items as the number of variables.
|
||||||
|
struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Statement> value; };
|
||||||
/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
|
/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
|
||||||
struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
|
struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
|
||||||
struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
|
struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
|
||||||
|
@ -122,6 +122,34 @@ assembly::Statement Parser::parseStatement()
|
|||||||
{
|
{
|
||||||
case Token::LParen:
|
case Token::LParen:
|
||||||
return parseCall(std::move(statement));
|
return parseCall(std::move(statement));
|
||||||
|
case Token::Comma:
|
||||||
|
{
|
||||||
|
// if a comma follows, a multiple assignment is assumed
|
||||||
|
|
||||||
|
if (statement.type() != typeid(assembly::Identifier))
|
||||||
|
fatalParserError("Label name / variable name must precede \",\" (multiple assignment).");
|
||||||
|
assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
|
||||||
|
|
||||||
|
Assignment assignment = createWithLocation<Assignment>(identifier.location);
|
||||||
|
assignment.variableNames.emplace_back(identifier);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
expectToken(Token::Comma);
|
||||||
|
statement = parseElementaryOperation(false);
|
||||||
|
if (statement.type() != typeid(assembly::Identifier))
|
||||||
|
fatalParserError("Variable name expected in multiple assignemnt.");
|
||||||
|
assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(statement));
|
||||||
|
}
|
||||||
|
while (currentToken() == Token::Comma);
|
||||||
|
|
||||||
|
expectToken(Token::Colon);
|
||||||
|
expectToken(Token::Assign);
|
||||||
|
|
||||||
|
assignment.value.reset(new Statement(parseExpression()));
|
||||||
|
assignment.location.end = locationOf(*assignment.value).end;
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
case Token::Colon:
|
case Token::Colon:
|
||||||
{
|
{
|
||||||
if (statement.type() != typeid(assembly::Identifier))
|
if (statement.type() != typeid(assembly::Identifier))
|
||||||
@ -136,7 +164,7 @@ assembly::Statement Parser::parseStatement()
|
|||||||
if (!m_julia && instructions().count(identifier.name))
|
if (!m_julia && instructions().count(identifier.name))
|
||||||
fatalParserError("Cannot use instruction names for identifier names.");
|
fatalParserError("Cannot use instruction names for identifier names.");
|
||||||
advance();
|
advance();
|
||||||
assignment.variableName = identifier;
|
assignment.variableNames.emplace_back(identifier);
|
||||||
assignment.value.reset(new Statement(parseExpression()));
|
assignment.value.reset(new Statement(parseExpression()));
|
||||||
assignment.location.end = locationOf(*assignment.value).end;
|
assignment.location.end = locationOf(*assignment.value).end;
|
||||||
return assignment;
|
return assignment;
|
||||||
|
@ -116,7 +116,11 @@ string AsmPrinter::operator()(assembly::StackAssignment const& _assignment)
|
|||||||
|
|
||||||
string AsmPrinter::operator()(assembly::Assignment const& _assignment)
|
string AsmPrinter::operator()(assembly::Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
return (*this)(_assignment.variableName) + " := " + boost::apply_visitor(*this, *_assignment.value);
|
solAssert(_assignment.variableNames.size() >= 1, "");
|
||||||
|
string variables = (*this)(_assignment.variableNames.front());
|
||||||
|
for (size_t i = 1; i < _assignment.variableNames.size(); ++i)
|
||||||
|
variables += ", " + (*this)(_assignment.variableNames[i]);
|
||||||
|
return variables + " := " + boost::apply_visitor(*this, *_assignment.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration)
|
string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration)
|
||||||
|
@ -249,6 +249,26 @@ BOOST_AUTO_TEST_CASE(recursion_depth)
|
|||||||
CHECK_ERROR(input, ParserError, "recursion");
|
CHECK_ERROR(input, ParserError, "recursion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(multiple_assignment)
|
||||||
|
{
|
||||||
|
CHECK_ERROR("{ let x:u256 function f() -> a:u256, b:u256 {} 123:u256, x := f() }", ParserError, "Label name / variable name must precede \",\" (multiple assignment).");
|
||||||
|
CHECK_ERROR("{ let x:u256 function f() -> a:u256, b:u256 {} x, 123:u256 := f() }", ParserError, "Variable name expected in multiple assignemnt.");
|
||||||
|
|
||||||
|
/// NOTE: Travis hiccups if not having a variable
|
||||||
|
char const* text = R"(
|
||||||
|
{
|
||||||
|
function f(a:u256) -> r1:u256, r2:u256 {
|
||||||
|
r1 := a
|
||||||
|
r2 := 7:u256
|
||||||
|
}
|
||||||
|
let x:u256 := 9:u256
|
||||||
|
let y:u256 := 2:u256
|
||||||
|
x, y := f(x)
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
BOOST_CHECK(successParse(text));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -412,7 +412,25 @@ BOOST_AUTO_TEST_CASE(recursion_depth)
|
|||||||
CHECK_PARSE_ERROR(input, ParserError, "recursion");
|
CHECK_PARSE_ERROR(input, ParserError, "recursion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(multiple_assignment)
|
||||||
|
{
|
||||||
|
CHECK_PARSE_ERROR("{ let x function f() -> a, b {} 123, x := f() }", ParserError, "Label name / variable name must precede \",\" (multiple assignment).");
|
||||||
|
CHECK_PARSE_ERROR("{ let x function f() -> a, b {} x, 123 := f() }", ParserError, "Variable name expected in multiple assignemnt.");
|
||||||
|
|
||||||
|
/// NOTE: Travis hiccups if not having a variable
|
||||||
|
char const* text = R"(
|
||||||
|
{
|
||||||
|
function f(a) -> r1, r2 {
|
||||||
|
r1 := a
|
||||||
|
r2 := 7
|
||||||
|
}
|
||||||
|
let x := 9
|
||||||
|
let y := 2
|
||||||
|
x, y := f(x)
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
BOOST_CHECK(successParse(text));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
|
@ -7867,6 +7867,31 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call)
|
|||||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7)));
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(inline_assembly_function_call_assignment)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract C {
|
||||||
|
function f() {
|
||||||
|
assembly {
|
||||||
|
let a1, b1, c1
|
||||||
|
function asmfun(a, b, c) -> x, y, z {
|
||||||
|
x := a
|
||||||
|
y := b
|
||||||
|
z := 7
|
||||||
|
}
|
||||||
|
a1, b1, c1 := asmfun(1, 2, 3)
|
||||||
|
mstore(0x00, a1)
|
||||||
|
mstore(0x20, b1)
|
||||||
|
mstore(0x40, c1)
|
||||||
|
return(0, 0x60)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 0, "C");
|
||||||
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7)));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(inline_assembly_function_call2)
|
BOOST_AUTO_TEST_CASE(inline_assembly_function_call2)
|
||||||
{
|
{
|
||||||
char const* sourceCode = R"(
|
char const* sourceCode = R"(
|
||||||
|
Loading…
Reference in New Issue
Block a user