Merge pull request #6589 from ethereum/addReturn

[Yul] Add code generation for "return" and for loops.
This commit is contained in:
chriseth 2019-04-30 17:07:45 +02:00 committed by GitHub
commit dc13469593
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 37 deletions

View File

@ -116,7 +116,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
{ {
string functionName = m_context.functionName(_function); string functionName = m_context.functionName(_function);
return m_context.functionCollector()->createFunction(functionName, [&]() { return m_context.functionCollector()->createFunction(functionName, [&]() {
Whiskers t("\nfunction <functionName>(<params>) <returns> {\n<body>\n}\n"); Whiskers t(R"(
function <functionName>(<params>) <returns> {
for { let return_flag := 1 } return_flag {} {
<body>
break
}
}
)");
t("functionName", functionName); t("functionName", functionName);
string params; string params;
for (auto const& varDecl: _function.parameters()) for (auto const& varDecl: _function.parameters())

View File

@ -22,6 +22,7 @@
#include <libsolidity/codegen/ir/IRGenerationContext.h> #include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h> #include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libyul/AsmPrinter.h> #include <libyul/AsmPrinter.h>
#include <libyul/AsmData.h> #include <libyul/AsmData.h>
@ -121,36 +122,101 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
" := " << " := " <<
expressionAsType(_assignment.rightHandSide(), *lvalue.annotation().type) << expressionAsType(_assignment.rightHandSide(), *lvalue.annotation().type) <<
"\n"; "\n";
m_code << "let " << m_context.variable(_assignment) << " := " << varName << "\n"; defineExpression(_assignment) << varName << "\n";
return false; return false;
} }
bool IRGeneratorForStatements::visit(Return const&) bool IRGeneratorForStatements::visit(ForStatement const& _for)
{ {
solUnimplemented("Return not yet implemented in yul code generation"); m_code << "for {\n";
if (_for.initializationExpression())
_for.initializationExpression()->accept(*this);
m_code << "} return_flag {\n";
if (_for.loopExpression())
_for.loopExpression()->accept(*this);
m_code << "}\n";
if (_for.condition())
{
_for.condition()->accept(*this);
m_code <<
"if iszero(" <<
expressionAsType(*_for.condition(), *TypeProvider::boolean()) <<
") { break }\n";
}
_for.body().accept(*this);
m_code << "}\n";
// Bubble up the return condition.
m_code << "if iszero(return_flag) { break }\n";
return false;
}
bool IRGeneratorForStatements::visit(Continue const&)
{
m_code << "continue\n";
return false;
}
bool IRGeneratorForStatements::visit(Break const&)
{
m_code << "break\n";
return false;
}
bool IRGeneratorForStatements::visit(Return const& _return)
{
if (Expression const* value = _return.expression())
{
solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
vector<ASTPointer<VariableDeclaration>> const& returnParameters =
_return.annotation().functionReturnParameters->parameters();
TypePointers types;
for (auto const& retVariable: returnParameters)
types.push_back(retVariable->annotation().type);
value->accept(*this);
// TODO support tuples
solUnimplementedAssert(types.size() == 1, "Multi-returns not implemented.");
m_code <<
m_context.variableName(*returnParameters.front()) <<
" := " <<
expressionAsType(*value, *types.front()) <<
"\n";
}
m_code << "return_flag := 0\n" << "break\n";
return false;
} }
void IRGeneratorForStatements::endVisit(BinaryOperation const& _binOp) void IRGeneratorForStatements::endVisit(BinaryOperation const& _binOp)
{ {
solUnimplementedAssert(_binOp.getOperator() == Token::Add, ""); solAssert(!!_binOp.annotation().commonType, "");
solUnimplementedAssert(*_binOp.leftExpression().annotation().type == *_binOp.rightExpression().annotation().type, ""); TypePointer commonType = _binOp.annotation().commonType;
if (IntegerType const* type = dynamic_cast<IntegerType const*>(_binOp.annotation().commonType))
{ if (_binOp.getOperator() == Token::And || _binOp.getOperator() == Token::Or)
solUnimplementedAssert(!type->isSigned(), ""); // special case: short-circuiting
m_code <<
"let " <<
m_context.variable(_binOp) <<
" := " <<
m_utils.overflowCheckedUIntAddFunction(type->numBits()) <<
"(" <<
m_context.variable(_binOp.leftExpression()) <<
", " <<
m_context.variable(_binOp.rightExpression()) <<
")\n";
}
else
solUnimplementedAssert(false, ""); solUnimplementedAssert(false, "");
else if (commonType->category() == Type::Category::RationalNumber)
defineExpression(_binOp) <<
toCompactHexWithPrefix(commonType->literalValue(nullptr)) <<
"\n";
else
{
solUnimplementedAssert(_binOp.getOperator() == Token::Add, "");
if (IntegerType const* type = dynamic_cast<IntegerType const*>(commonType))
{
solUnimplementedAssert(!type->isSigned(), "");
defineExpression(_binOp) <<
m_utils.overflowCheckedUIntAddFunction(type->numBits()) <<
"(" <<
expressionAsType(_binOp.leftExpression(), *commonType) <<
", " <<
expressionAsType(_binOp.rightExpression(), *commonType) <<
")\n";
}
else
solUnimplementedAssert(false, "");
}
} }
bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall) bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
@ -169,10 +235,7 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion"); solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
_functionCall.arguments().front()->accept(*this); _functionCall.arguments().front()->accept(*this);
m_code << defineExpression(_functionCall) <<
"let " <<
m_context.variable(_functionCall) <<
" := " <<
expressionAsType(*_functionCall.arguments().front(), *_functionCall.annotation().type) << expressionAsType(*_functionCall.arguments().front(), *_functionCall.annotation().type) <<
"\n"; "\n";
@ -225,10 +288,7 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration)) if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
{ {
// @TODO The function can very well return multiple vars. // @TODO The function can very well return multiple vars.
m_code << defineExpression(_functionCall) <<
"let " <<
m_context.variable(_functionCall) <<
" := " <<
m_context.virtualFunctionName(*functionDef) << m_context.virtualFunctionName(*functionDef) <<
"(" << "(" <<
joinHumanReadable(args) << joinHumanReadable(args) <<
@ -241,10 +301,7 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
// @TODO The function can very well return multiple vars. // @TODO The function can very well return multiple vars.
args = vector<string>{m_context.variable(_functionCall.expression())} + args; args = vector<string>{m_context.variable(_functionCall.expression())} + args;
m_code << defineExpression(_functionCall) <<
"let " <<
m_context.variable(_functionCall) <<
" := " <<
m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) << m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) <<
"(" << "(" <<
joinHumanReadable(args) << joinHumanReadable(args) <<
@ -279,7 +336,7 @@ bool IRGeneratorForStatements::visit(Identifier const& _identifier)
value = m_context.variableName(*varDecl); value = m_context.variableName(*varDecl);
else else
solUnimplemented(""); solUnimplemented("");
m_code << "let " << m_context.variable(_identifier) << " := " << value << "\n"; defineExpression(_identifier) << value << "\n";
return false; return false;
} }
@ -292,7 +349,7 @@ bool IRGeneratorForStatements::visit(Literal const& _literal)
case Type::Category::RationalNumber: case Type::Category::RationalNumber:
case Type::Category::Bool: case Type::Category::Bool:
case Type::Category::Address: case Type::Category::Address:
m_code << "let " << m_context.variable(_literal) << " := " << toCompactHexWithPrefix(type->literalValue(&_literal)) << "\n"; defineExpression(_literal) << toCompactHexWithPrefix(type->literalValue(&_literal)) << "\n";
break; break;
case Type::Category::StringLiteral: case Type::Category::StringLiteral:
solUnimplemented(""); solUnimplemented("");
@ -313,3 +370,8 @@ string IRGeneratorForStatements::expressionAsType(Expression const& _expression,
else else
return m_utils.conversionFunction(from, _to) + "(" + std::move(varName) + ")"; return m_utils.conversionFunction(from, _to) + "(" + std::move(varName) + ")";
} }
ostream& IRGeneratorForStatements::defineExpression(Expression const& _expression)
{
return m_code << "let " << m_context.variable(_expression) << " := ";
}

View File

@ -46,6 +46,9 @@ public:
bool visit(VariableDeclarationStatement const& _variableDeclaration) override; bool visit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Assignment const& _assignment) override; bool visit(Assignment const& _assignment) override;
bool visit(ForStatement const& _forStatement) override;
bool visit(Continue const& _continueStatement) override;
bool visit(Break const& _breakStatement) override;
bool visit(Return const& _return) override; bool visit(Return const& _return) override;
void endVisit(BinaryOperation const& _binOp) override; void endVisit(BinaryOperation const& _binOp) override;
bool visit(FunctionCall const& _funCall) override; bool visit(FunctionCall const& _funCall) override;
@ -57,6 +60,7 @@ private:
/// @returns a Yul expression representing the current value of @a _expression, /// @returns a Yul expression representing the current value of @a _expression,
/// converted to type @a _to if it does not yet have that type. /// converted to type @a _to if it does not yet have that type.
std::string expressionAsType(Expression const& _expression, Type const& _to); std::string expressionAsType(Expression const& _expression, Type const& _to);
std::ostream& defineExpression(Expression const& _expression);
std::ostringstream m_code; std::ostringstream m_code;
IRGenerationContext& m_context; IRGenerationContext& m_context;

View File

@ -1 +1 @@
{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue()\n {\n revert(0, 0)\n }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default {\n }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0)\n {\n revert(0, 0)\n }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n {\n tail := add(headStart, 0)\n }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))\n {\n revert(0, 0)\n }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n }\n function shift_right_224_unsigned(value) -> newValue\n {\n newValue := shr(224, value)\n }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}} {"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue()\n {\n revert(0, 0)\n }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default {\n }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0)\n {\n revert(0, 0)\n }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n {\n tail := add(headStart, 0)\n }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))\n {\n revert(0, 0)\n }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n for {\n let return_flag := 1\n }\n return_flag\n {\n }\n {\n break\n }\n }\n function shift_right_224_unsigned(value) -> newValue\n {\n newValue := shr(224, value)\n }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}}

View File

@ -1 +1 @@
{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\nfunction fun_f_5() {\n\n}\n\n\t\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\t\tnewValue := shr(224, value)\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}} {"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\t\tnewValue := shr(224, value)\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}}

View File

@ -0,0 +1,40 @@
contract C {
function f() public returns (uint x) {
x = 1;
for (uint a = 0; a < 10; a = a + 1) {
x = x + x;
}
}
function g() public returns (uint x) {
x = 1;
for (uint a = 0; a < 10; a = a + 1) {
x = x + x;
break;
}
}
function h() public returns (uint x) {
x = 1;
uint a = 0;
for (; a < 10; a = a + 1) {
continue;
x = x + x;
}
x = x + a;
}
function i() public returns (uint x) {
x = 1;
uint a;
for (; a < 10; a = a + 1) {
return x;
x = x + x;
}
x = x + a;
}
}
// ===
// compileViaYul: true
// ----
// f() -> 1024
// g() -> 2
// h() -> 11
// i() -> 1

View File

@ -0,0 +1,10 @@
contract C {
function f() public pure returns (uint x) {
return 7;
x = 3;
}
}
// ====
// compileViaYul: true
// ----
// f() -> 7

View File

@ -0,0 +1,11 @@
contract C {
function f() public pure returns (uint) {
uint8 b;
assembly { b := 0xffff }
return b;
}
}
// ====
// compileViaYul: true
// ----
// f() -> 255