Merge pull request #3220 from ethereum/IuliaIf

If statement for Iulia / Inline Assembly
This commit is contained in:
chriseth 2017-11-27 09:02:46 -05:00 committed by GitHub
commit a1f59cbb17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 163 additions and 11 deletions

View File

@ -5,6 +5,7 @@ Features:
* Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature.
* Type Checker: Improve address checksum warning.
* Type Checker: More detailed errors for invalid array lengths (such as division by zero).
* Inline Assembly: ``if`` statement.
Bugfixes:

View File

@ -26,6 +26,7 @@ arising when writing manual assembly by the following features:
* access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }``
* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))``
* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }``
* if statements: ``if slt(x, 0) { x := sub(0, x) }``
* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }``
* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }``
@ -400,7 +401,7 @@ Labels
Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses
which can change easily. Solidity inline assembly provides labels to make the use of
jumps easier. Note that labels are a low-level feature and it is possible to write
efficient assembly without labels, just using assembly functions, loops and switch instructions
efficient assembly without labels, just using assembly functions, loops, if and switch instructions
(see below). The following code computes an element in the Fibonacci series.
.. code::
@ -523,6 +524,21 @@ is performed by replacing the variable's value on the stack by the new value.
=: v // instruction style assignment, puts the result of sload(10) into v
}
If
--
The if statement can be used for conditionally executing code.
There is no "else" part, consider using "switch" (see below) if
you need multiple alternatives.
.. code::
{
if eq(value, 0) { revert(0, 0) }
}
The curly braces for the body are required.
Switch
------
@ -622,7 +638,7 @@ Things to Avoid
---------------
Inline assembly might have a quite high-level look, but it actually is extremely
low-level. Function calls, loops and switches are converted by simple
low-level. Function calls, loops, ifs and switches are converted by simple
rewriting rules and after that, the only thing the assembler does for you is re-arranging
functional-style opcodes, managing jump labels, counting stack height for
variable access and removing stack slots for assembly-local variables when the end
@ -669,7 +685,7 @@ for the Solidity compiler. In this form, it tries to achieve several goals:
3. Control flow should be easy to detect to help in formal verification and optimization.
In order to achieve the first and last goal, assembly provides high-level constructs
like ``for`` loops, ``switch`` statements and function calls. It should be possible
like ``for`` loops, ``if`` and ``switch`` statements and function calls. It should be possible
to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``,
``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow
and the last two obfuscate control flow. Furthermore, functional statements of
@ -875,6 +891,7 @@ Grammar::
FunctionalAssemblyAssignment |
AssemblyAssignment |
LabelDefinition |
AssemblyIf |
AssemblySwitch |
AssemblyFunctionDefinition |
AssemblyFor |
@ -891,6 +908,7 @@ Grammar::
IdentifierList = Identifier ( ',' Identifier)*
AssemblyAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblyIf = 'if' FunctionalAssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
( 'default' AssemblyBlock )?
AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock

View File

@ -15,7 +15,7 @@ future versions of the Solidity compiler will even use JULIA as intermediate
language. It should also be easy to build high-level optimizer stages for JULIA.
The core components of JULIA are functions, blocks, variables, literals,
for-loops, switch-statements, expressions and assignments to variables.
for-loops, if-statements, switch-statements, expressions and assignments to variables.
JULIA is typed, both variables and literals must specify the type with postfix
notation. The supported types are ``bool``, ``u8``, ``s8``, ``u32``, ``s32``,
@ -88,6 +88,8 @@ Grammar::
IdentifierList ':=' Expression
Expression =
FunctionCall | Identifier | Literal
If =
'if' Expression Block
Switch =
'switch' Expression Case* ( 'default' Block )?
Case =
@ -248,8 +250,14 @@ We will use a destructuring notation for the AST nodes.
G, L, break
E(G, L, continue: BreakContinue) =
G, L, continue
E(G, L, <if condition body>: If) =
let G0, L0, v = E(G, L, condition)
if v is true:
E(G0, L0, body)
else:
G0, L0, regular
E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn>: Switch) =
E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {}) =
E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {})
E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn default st'>: Switch) =
let G0, L0, v = E(G, L, condition)
// i = 1 .. n

View File

@ -217,6 +217,19 @@ void CodeTransform::operator()(assembly::Instruction const& _instruction)
checkStackHeight(&_instruction);
}
void CodeTransform::operator()(If const& _if)
{
visitExpression(*_if.condition);
m_assembly.setSourceLocation(_if.location);
m_assembly.appendInstruction(solidity::Instruction::ISZERO);
AbstractAssembly::LabelID end = m_assembly.newLabelId();
m_assembly.appendJumpToIf(end);
(*this)(_if.body);
m_assembly.setSourceLocation(_if.location);
m_assembly.appendLabel(end);
checkStackHeight(&_if);
}
void CodeTransform::operator()(Switch const& _switch)
{
//@TODO use JUMPV in EVM1.5?

View File

@ -104,6 +104,7 @@ public:
void operator()(solidity::assembly::StackAssignment const& _assignment);
void operator()(solidity::assembly::Assignment const& _assignment);
void operator()(solidity::assembly::VariableDeclaration const& _varDecl);
void operator()(solidity::assembly::If const& _if);
void operator()(solidity::assembly::Switch const& _switch);
void operator()(solidity::assembly::FunctionDefinition const&);
void operator()(solidity::assembly::ForLoop const&);

View File

@ -72,6 +72,11 @@ public:
for (auto const& arg: _funCall.arguments)
boost::apply_visitor(*this, arg);
}
void operator()(assembly::If const& _if)
{
boost::apply_visitor(*this, *_if.condition);
(*this)(_if.body);
}
void operator()(assembly::Switch const& _switch)
{
boost::apply_visitor(*this, *_switch.expression);

View File

@ -168,7 +168,7 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
{
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
solAssert(members > 0, "empty enum should have caused a parser error.");
Whiskers w("switch lt(value, <members>) case 0 { <failure> } cleaned := value");
Whiskers w("if iszero(lt(value, <members>)) { <failure> } cleaned := value");
w("members", to_string(members));
if (_revertOnFailure)
w("failure", "revert(0, 0)");
@ -988,8 +988,8 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
{
mstore(add(dst, i), mload(add(src, i)))
}
switch eq(i, length)
case 0 {
if gt(i, length)
{
// clear end
mstore(add(dst, length), 0)
}

View File

@ -286,6 +286,22 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
return success;
}
bool AsmAnalyzer::operator()(If const& _if)
{
bool success = true;
if (!expectExpression(*_if.condition))
success = false;
m_stackHeight--;
if (!(*this)(_if.body))
success = false;
m_info.stackHeightInfo[&_if] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(Switch const& _switch)
{
bool success = true;

View File

@ -70,6 +70,7 @@ public:
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const& _functionCall);
bool operator()(assembly::If const& _if);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::ForLoop const& _forLoop);
bool operator()(assembly::Block const& _block);

View File

@ -68,6 +68,8 @@ struct VariableDeclaration { SourceLocation location; TypedNameList variables; s
struct Block { SourceLocation location; std::vector<Statement> statements; };
/// Function definition ("function f(a, b) -> (d, e) { ... }")
struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; };
/// Conditional execution without "else" part.
struct If { SourceLocation location; std::shared_ptr<Statement> condition; Block body; };
/// Switch case or default case
struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; };
/// Switch statement

View File

@ -41,11 +41,12 @@ struct VariableDeclaration;
struct FunctionalInstruction;
struct FunctionDefinition;
struct FunctionCall;
struct If;
struct Switch;
struct ForLoop;
struct Block;
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>;
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
}
}

View File

@ -73,13 +73,23 @@ assembly::Statement Parser::parseStatement()
return parseFunctionDefinition();
case Token::LBrace:
return parseBlock();
case Token::If:
{
assembly::If _if = createWithLocation<assembly::If>();
m_scanner->next();
_if.condition = make_shared<Statement>(parseExpression());
if (_if.condition->type() == typeid(assembly::Instruction))
fatalParserError("Instructions are not supported as conditions for if - try to append \"()\".");
_if.body = parseBlock();
return _if;
}
case Token::Switch:
{
assembly::Switch _switch = createWithLocation<assembly::Switch>();
m_scanner->next();
_switch.expression = make_shared<Statement>(parseExpression());
if (_switch.expression->type() == typeid(assembly::Instruction))
fatalParserError("Instructions are not supported as expressions for switch.");
fatalParserError("Instructions are not supported as expressions for switch - try to append \"()\".");
while (m_scanner->currentToken() == Token::Case)
_switch.cases.emplace_back(parseCase());
if (m_scanner->currentToken() == Token::Default)

View File

@ -174,6 +174,11 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
")";
}
string AsmPrinter::operator()(If const& _if)
{
return "if " + boost::apply_visitor(*this, *_if.condition) + "\n" + (*this)(_if.body);
}
string AsmPrinter::operator()(Switch const& _switch)
{
string out = "switch " + boost::apply_visitor(*this, *_switch.expression);

View File

@ -48,6 +48,7 @@ public:
std::string operator()(assembly::VariableDeclaration const& _variableDeclaration);
std::string operator()(assembly::FunctionDefinition const& _functionDefinition);
std::string operator()(assembly::FunctionCall const& _functionCall);
std::string operator()(assembly::If const& _if);
std::string operator()(assembly::Switch const& _switch);
std::string operator()(assembly::ForLoop const& _forLoop);
std::string operator()(assembly::Block const& _block);

View File

@ -104,6 +104,11 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
return success;
}
bool ScopeFiller::operator()(If const& _if)
{
return (*this)(_if.body);
}
bool ScopeFiller::operator()(Switch const& _switch)
{
bool success = true;

View File

@ -59,6 +59,7 @@ public:
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const&) { return true; }
bool operator()(assembly::If const& _if);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::ForLoop const& _forLoop);
bool operator()(assembly::Block const& _block);

View File

@ -269,6 +269,21 @@ BOOST_AUTO_TEST_CASE(multiple_assignment)
BOOST_CHECK(successParse(text));
}
BOOST_AUTO_TEST_CASE(if_statement)
{
BOOST_CHECK(successParse("{ if true:bool {} }"));
BOOST_CHECK(successParse("{ if false:bool { let x:u256 := 3:u256 } }"));
BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }"));
}
BOOST_AUTO_TEST_CASE(if_statement_invalid)
{
CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected.");
CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected token LBrace");
// TODO change this to an error once we check types.
BOOST_CHECK(successParse("{ if 42:u256 { } }"));
}
BOOST_AUTO_TEST_SUITE_END()
}

View File

@ -251,6 +251,27 @@ BOOST_AUTO_TEST_CASE(variable_use_before_decl)
CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared.");
}
BOOST_AUTO_TEST_CASE(if_statement)
{
BOOST_CHECK(successParse("{ if 42 {} }"));
BOOST_CHECK(successParse("{ if 42 { let x := 3 } }"));
BOOST_CHECK(successParse("{ function f() -> x {} if f() { pop(f()) } }"));
}
BOOST_AUTO_TEST_CASE(if_statement_scope)
{
BOOST_CHECK(successParse("{ let x := 2 if 42 { x := 3 } }"));
CHECK_PARSE_ERROR("{ if 32 { let x := 3 } x := 2 }", DeclarationError, "Variable not found or variable not lvalue.");
}
BOOST_AUTO_TEST_CASE(if_statement_invalid)
{
CHECK_PARSE_ERROR("{ if calldatasize {}", ParserError, "Instructions are not supported as conditions for if");
BOOST_CHECK("{ if calldatasize() {}");
CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace");
}
BOOST_AUTO_TEST_CASE(switch_statement)
{
BOOST_CHECK(successParse("{ switch 42 default {} }"));
@ -275,7 +296,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case)
BOOST_AUTO_TEST_CASE(switch_invalid_expression)
{
CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected.");
CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch.");
CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch");
CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
}
@ -487,6 +508,11 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
parsePrintCompare(parsed);
}
BOOST_AUTO_TEST_CASE(print_if)
{
parsePrintCompare("{\n if 2\n {\n pop(mload(0))\n }\n}");
}
BOOST_AUTO_TEST_CASE(print_switch)
{
parsePrintCompare("{\n switch 42\n case 1 {\n }\n case 2 {\n }\n default {\n }\n}");
@ -628,6 +654,11 @@ BOOST_AUTO_TEST_CASE(for_statement)
BOOST_CHECK(successAssemble("{ let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } }"));
}
BOOST_AUTO_TEST_CASE(if_statement)
{
BOOST_CHECK(successAssemble("{ if 1 {} }"));
BOOST_CHECK(successAssemble("{ let x := 0 if eq(calldatasize(), 0) { x := 1 } mstore(0, x) }"));
}
BOOST_AUTO_TEST_CASE(large_constant)
{

View File

@ -8032,6 +8032,24 @@ BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call)
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_if)
{
char const* sourceCode = R"(
contract C {
function f(uint a) returns (uint b) {
assembly {
if gt(a, 1) { b := 2 }
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0)));
ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0)));
ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2)));
ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_switch)
{
char const* sourceCode = R"(