mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #3220 from ethereum/IuliaIf
If statement for Iulia / Inline Assembly
This commit is contained in:
commit
a1f59cbb17
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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&);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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"(
|
||||
|
Loading…
Reference in New Issue
Block a user