Remove stack annotations from documentation.

This commit is contained in:
chriseth 2017-02-28 11:12:58 +01:00
parent 369005fc03
commit 8c1d949d1a

View File

@ -337,8 +337,6 @@ in solidity are:
This feature is still a bit cumbersome to use, because the stack offset essentially 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. changes during the call, and thus references to local variables will be wrong.
The assumed stack height can be changed by using labels: ``funreturn[-2]:``
The number in brackets should be `m - n - 1`.
.. code:: .. code::
@ -415,19 +413,23 @@ to the stack height just prior to the label.
Note that you will not have to care about these things if you just use Note that you will not have to care about these things if you just use
loops and assembly-level functions. loops and assembly-level functions.
As an example how this can be done in extreme cases, please see the following.
.. code:: .. code::
{ {
let x := 8 let x := 8
jump(two) jump(two)
one[1]: 0 // This code is unreachable but will adjust the stack height correctly
// Now the stack height is correctly adjusted. one:
x := 9 x := 9 // Now x can be accessed properly.
jump(three) jump(three)
pop // Similar negative correction.
two: two:
7 // push something onto the stack 7 // push something onto the stack
jump(one) jump(one)
three: three:
pop // We have to pop the manually pushed value here again.
} }
.. note:: .. note::
@ -652,17 +654,12 @@ for e.g. returning runtime code or creating contracts. No identifier from an
outer assembly is visible in a sub-assembly. outer assembly is visible in a sub-assembly.
If control flow passes over the end of a block, pop instructions are inserted If control flow passes over the end of a block, pop instructions are inserted
that match the number of local variables declared in that block, unless the that match the number of local variables declared in that block.
``}`` is directly preceded by an opcode that does not have a continuing control Whenever a local variable is referenced, the code generator needs
flow path. Whenever a local variable is referenced, the code generator needs
to know its current relative position in the stack and thus it needs to to know its current relative position in the stack and thus it needs to
keep track of the current so-called stack height. keep track of the current so-called stack height. Since all local variables
At the end of a block, this implicit stack height is always reduced by the number are removed at the end of a block, the stack height before and after the block
of local variables whether there is a continuing control flow or not. should be the same. If this is not the case, a warning is issued.
This means that the stack height before and after the block should be the same.
If this is not the case, a warning is issued,
unless the last instruction in the block did not have a continuing control flow path.
Why do we use higher-level constructs like ``switch``, ``for`` and functions: Why do we use higher-level constructs like ``switch``, ``for`` and functions:
@ -674,10 +671,9 @@ verification and optimization.
Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. Furthermore, if manual jumps are allowed, computing the stack height is rather complicated.
The position of all local variables on the stack needs to be known, otherwise The position of all local variables on the stack needs to be known, otherwise
neither references to local variables nor removing local variables automatically neither references to local variables nor removing local variables automatically
from the stack at the end of a block will work properly. Because of that, from the stack at the end of a block will work properly. The desugaring
every label that is preceded by an instruction that ends or diverts control flow mechanism correctly inserts operations at unreachable blocks that adjust the
should be annotated with the current stack layout. This annotation is performed stack height properly in case of jumps that do not have a continuing control flow.
automatically during the desugaring phase.
Example: Example:
@ -730,17 +726,21 @@ After the desugaring phase it looks as follows::
$case1: $case1:
{ {
// the function call - we put return label and arguments on the stack // the function call - we put return label and arguments on the stack
$ret1 calldataload(4) jump($fun_f) $ret1 calldataload(4) jump(f)
$ret1 [r]: // a label with a [...]-annotation resets the stack height // This is unreachable code. Opcodes are added that mirror the
// to "current block + number of local variables". It also // effect of the function on the stack height: Arguments are
// introduces a variable, r: // removed and return values are introduced.
// r is at top of stack, $0 is below (from enclosing block) pop pop
$ret2 0x20 jump($fun_allocate) let r := 0
$ret2 [ret]: // stack here: $0, r, ret (top) $ret1: // the actual return point
$ret2 0x20 jump($allocate)
pop pop let ret := 0
$ret2:
mstore(ret, r) mstore(ret, r)
return(ret, 0x20) return(ret, 0x20)
// although it is useless, the jump is automatically inserted, // although it is useless, the jump is automatically inserted,
// since the desugaring process does not analyze control-flow // since the desugaring process is a purely syntactic operation that
// does not analyze control-flow
jump($endswitch) jump($endswitch)
} }
$caseDefault: $caseDefault:
@ -751,20 +751,29 @@ After the desugaring phase it looks as follows::
$endswitch: $endswitch:
} }
jump($afterFunction) jump($afterFunction)
$fun_allocate: allocate:
{ {
$start[$retpos, size]: // we jump over the unreachable code that introduces the function arguments
// output variables live in the same scope as the arguments. jump($start)
let $retpos := 0 let size := 0
$start:
// output variables live in the same scope as the arguments and is
// actually allocated.
let pos := 0 let pos := 0
{ {
pos := mload(0x40) pos := mload(0x40)
mstore(0x40, add(pos, size)) mstore(0x40, add(pos, size))
} }
// This code replaces the arguments by the return values and jumps back.
swap1 pop swap1 jump swap1 pop swap1 jump
// Again unreachable code that corrects stack height.
0 0
} }
$fun_f: f:
{ {
start [$retpos, x]: jump($start)
let $retpos := 0 let x := 0
$start:
let y := 0 let y := 0
{ {
let i := 0 let i := 0
@ -777,8 +786,9 @@ After the desugaring phase it looks as follows::
{ i := add(i, 1) } { i := add(i, 1) }
jump($for_begin) jump($for_begin)
$for_end: $for_end:
} // Here, a pop instruction is inserted for i } // Here, a pop instruction will be inserted for i
swap1 pop swap1 jump swap1 pop swap1 jump
0 0
} }
$afterFunction: $afterFunction:
stop stop
@ -839,7 +849,7 @@ Grammar::
IdentifierOrList = Identifier | '(' IdentifierList ')' IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)* IdentifierList = Identifier ( ',' Identifier)*
AssemblyAssignment = '=:' Identifier AssemblyAssignment = '=:' Identifier
LabelDefinition = Identifier ( '[' ( IdentifierList? | NumberLiteral ) ']' )? ':' LabelDefinition = Identifier ':'
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase* AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
( 'default' ':' AssemblyBlock )? ( 'default' ':' AssemblyBlock )?
AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock
@ -872,11 +882,14 @@ Pseudocode::
AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) -> AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) ->
<name>: <name>:
{ {
$<name>_start [$retPC, $argn, ..., arg1]: jump($<name>_start)
let $retPC := 0 let argn := 0 ... let arg1 := 0
$<name>_start:
let ret1 := 0 ... let retm := 0 let ret1 := 0 ... let retm := 0
{ desugar(body) } { desugar(body) }
swap and pop items so that only ret1, ... retn, $retPC are left on the stack swap and pop items so that only ret1, ... retm, $retPC are left on the stack
jump jump
0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC
} }
AssemblyFor('for' { init } condition post body) -> AssemblyFor('for' { init } condition post body) ->
{ {
@ -896,6 +909,7 @@ Pseudocode::
pop all local variables that are defined at the current point pop all local variables that are defined at the current point
but not at $forI_end but not at $forI_end
jump($forI_end) jump($forI_end)
0 (as many as variables were removed above)
} }
'continue' -> 'continue' ->
{ {
@ -903,6 +917,7 @@ Pseudocode::
pop all local variables that are defined at the current point pop all local variables that are defined at the current point
but not at $forI_continue but not at $forI_continue
jump($forI_continue) jump($forI_continue)
0 (as many as variables were removed above)
} }
AssemblySwitch(switch condition cases ( default: defaultBlock )? ) -> AssemblySwitch(switch condition cases ( default: defaultBlock )? ) ->
{ {
@ -924,10 +939,13 @@ Pseudocode::
{ {
// find I such that $funcallI_* does not exist // find I such that $funcallI_* does not exist
$funcallI_return argn ... arg2 arg1 jump(<name>) $funcallI_return argn ... arg2 arg1 jump(<name>)
pop (n + 1 times)
if the current context is `let (id1, ..., idm) := f(...)` -> if the current context is `let (id1, ..., idm) := f(...)` ->
$funcallI_return [id1, ..., idm]: let id1 := 0 ... let idm := 0
$funcallI_return:
else -> else ->
$funcallI_return[m - n - 1]: 0 (m times)
$funcallI_return:
turn the functional expression that leads to the function call turn the functional expression that leads to the function call
into a statement stream into a statement stream
} }
@ -987,16 +1005,8 @@ Pseudocode::
look up id in the syntactic stack of blocks, assert that it is a variable look up id in the syntactic stack of blocks, assert that it is a variable
SWAPi where i = 1 + stack_height - stack_height_of_identifier(id) SWAPi where i = 1 + stack_height - stack_height_of_identifier(id)
POP POP
LabelDefinition(name [id1, ..., idn] :) -> LabelDefinition(name:) ->
JUMPDEST JUMPDEST
// register new local variables id1, ..., idn and adjust the stack
// height such that it matches the stack height at the beginning of
// the block plus all local variables including the just registered
// ones where idn is at the stack top. If n is zero, resets the stack
// height to just the local variables.
LabelDefinition(name [number] :) ->
JUMPDEST
// adjust stack height by the relative amount "number" (can be negative)
NumberLiteral(num) -> NumberLiteral(num) ->
PUSH<num interpreted as decimal and right-aligned> PUSH<num interpreted as decimal and right-aligned>
HexLiteral(lit) -> HexLiteral(lit) ->