Recursion with Frames in Memory
See ethereum/solidity#9622 (comment)
Code Used
object "RecursionStackOverflow" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
let currentMemoryFrame := 4096 // or whatever memory offset is beyond what the rest of the code uses
// in more complex cases this will have to use some full-blown memory management mechanism
// set call arguments:
mstore(add(currentMemoryFrame, 0x20), 0) // first argument
mstore(add(currentMemoryFrame, 0x40), 0) // second argument
// MEMORY_FRAME_POINTER
mstore(0x20, currentMemoryFrame)
eval()
// fetch return values
let end := mload(add(currentMemoryFrame, 0x60))
let response := mload(add(currentMemoryFrame, 0x80))
function eval() {
{
// fetch current memory frame
let _currentMemoryFrame := mload(0x20)
// fetch arguments
let data_ptr := mload(add(_currentMemoryFrame, 0x20))
let env_ptr := mload(add(_currentMemoryFrame, 0x40))
let stepcount := mload(0)
if gt(stepcount, 169) { mstore(0, stepcount) return(0, 32) }
mstore(0, add(stepcount, 1))
let rootid := 3
switch rootid
// number
case 1 {
}
// function
case 3 {
}
default {
}
// prepare memory frame for the next recursive call
let nextMemoryFrame := add(_currentMemoryFrame, 0xa0)
// save the current memory frame pointer in the next memory frame
mstore(nextMemoryFrame, _currentMemoryFrame)
// set arguments for the next recursive call
mstore(add(nextMemoryFrame, 0x20), data_ptr)
mstore(add(nextMemoryFrame, 0x40), env_ptr)
// update memory frame for recursive call
mstore(0x20, nextMemoryFrame)
} // The entire stack should be popped at the end of the block. Note that the Yul optimizer must be enabled for that to work.
// it's important that there is not a single local variable in scope here, but everything is stored in memory instead
eval()
{
// fetch memory frame of the recursive call
let recursiveCallMemoryFramePointer := mload(0x20)
// fetch our own memory frame
let currentMemoryFramePointer := mload(recursiveCallMemoryFramePointer)
// fetch the return values of the recursive call
let ee := mload(add(recursiveCallMemoryFramePointer, 0x60))
let rr := mload(add(recursiveCallMemoryFramePointer, 0x80))
// restore to original memory frame pointer
mstore(0x20, currentMemoryFramePointer)
let end_ptr, result_ptr
/// ... further computation calculating end_ptr and result_ptr
// store our return values
mstore(add(currentMemoryFramePointer, 0x60), end_ptr)
mstore(add(currentMemoryFramePointer, 0x80), result_ptr)
}
}
}}
}
Solc.js 0.7.0
607c8061000f600039806000f350fe6000611020526000611040526110006020526017601b565b607b565b602080518181015160408201516000805160a9811115603b578082528582f35b6001810182525060a084018481528360c08601528260e086015280865250605f601b565b845151935083855280606085015280608085015250505050505b565b
Solc 0.7.0
solc --strict-assembly --optimize --optimize-runs=200 contracts/recursive_test.sol
With out without the unnamed blocks {}
there was no compilation difference.
======= contracts/taylor_v2_tay.sol (EVM) =======
Pretty printed source:
object "RecursionStackOverflow" {
code {
{
let _1 := datasize("Runtime")
datacopy(0, dataoffset("Runtime"), _1)
return(0, _1)
}
}
object "Runtime" {
code {
{
mstore(4128, 0)
mstore(4160, 0)
mstore(0x20, 4096)
eval()
}
function eval()
{
let _1 := 0x20
let _currentMemoryFrame := mload(_1)
let data_ptr := mload(add(_currentMemoryFrame, _1))
let env_ptr := mload(add(_currentMemoryFrame, 0x40))
let _2 := 0
let stepcount := mload(_2)
if gt(stepcount, 169)
{
mstore(_2, stepcount)
return(_2, _1)
}
mstore(_2, add(stepcount, 1))
let nextMemoryFrame := add(_currentMemoryFrame, 0xa0)
mstore(nextMemoryFrame, _currentMemoryFrame)
mstore(add(_currentMemoryFrame, 192), data_ptr)
mstore(add(_currentMemoryFrame, 224), env_ptr)
mstore(_1, nextMemoryFrame)
eval()
let currentMemoryFramePointer := mload(mload(_1))
mstore(_1, currentMemoryFramePointer)
mstore(add(currentMemoryFramePointer, 0x60), _2)
mstore(add(currentMemoryFramePointer, 0x80), _2)
}
}
}
}
Binary representation:
607c8061000f600039806000f350fe6000611020526000611040526110006020526017601b565b607b565b602080518181015160408201516000805160a9811115603b578082528582f35b6001810182525060a084018481528360c08601528260e086015280865250605f601b565b845151935083855280606085015280608085015250505050505b565b
Text representation:
dataSize(sub_0)
/* "contracts/taylor_v2_tay.sol":88:107 */
dup1
dataOffset(sub_0)
/* "contracts/taylor_v2_tay.sol":62:63 */
0x00
/* "contracts/taylor_v2_tay.sol":53:108 */
codecopy
/* "contracts/taylor_v2_tay.sol":127:146 */
dup1
/* "contracts/taylor_v2_tay.sol":62:63 */
0x00
/* "contracts/taylor_v2_tay.sol":117:147 */
return
pop
stop
sub_0: assembly {
/* "contracts/taylor_v2_tay.sol":474:475 */
0x00
/* "contracts/taylor_v2_tay.sol":443:472 */
0x1020
/* "contracts/taylor_v2_tay.sol":436:476 */
mstore
/* "contracts/taylor_v2_tay.sol":474:475 */
0x00
/* "contracts/taylor_v2_tay.sol":510:539 */
0x1040
/* "contracts/taylor_v2_tay.sol":503:543 */
mstore
/* "contracts/taylor_v2_tay.sol":223:227 */
0x1000
/* "contracts/taylor_v2_tay.sol":467:471 */
0x20
/* "contracts/taylor_v2_tay.sol":603:635 */
mstore
/* "contracts/taylor_v2_tay.sol":645:651 */
tag_1
tag_2
jump // in
tag_1:
/* "contracts/taylor_v2_tay.sol":820:3431 */
jump(tag_3)
tag_2:
/* "contracts/taylor_v2_tay.sol":950:954 */
0x20
dup1
/* "contracts/taylor_v2_tay.sol":944:955 */
mload
/* "contracts/taylor_v2_tay.sol":950:954 */
dup2
/* "contracts/taylor_v2_tay.sol":1033:1052 */
dup2
/* "contracts/taylor_v2_tay.sol":1029:1059 */
add
/* "contracts/taylor_v2_tay.sol":1023:1060 */
mload
/* "contracts/taylor_v2_tay.sol":1123:1127 */
0x40
/* "contracts/taylor_v2_tay.sol":1102:1121 */
dup3
/* "contracts/taylor_v2_tay.sol":1098:1128 */
add
/* "contracts/taylor_v2_tay.sol":1092:1129 */
mload
/* "contracts/taylor_v2_tay.sol":1170:1171 */
0x00
dup1
/* "contracts/taylor_v2_tay.sol":1164:1172 */
mload
/* "contracts/taylor_v2_tay.sol":1206:1209 */
0xa9
/* "contracts/taylor_v2_tay.sol":1195:1204 */
dup2
/* "contracts/taylor_v2_tay.sol":1192:1210 */
gt
/* "contracts/taylor_v2_tay.sol":1189:1191 */
iszero
tag_5
jumpi
/* "contracts/taylor_v2_tay.sol":1223:1232 */
dup1
/* "contracts/taylor_v2_tay.sol":1170:1171 */
dup3
/* "contracts/taylor_v2_tay.sol":1213:1233 */
mstore
/* "contracts/taylor_v2_tay.sol":950:954 */
dup6
/* "contracts/taylor_v2_tay.sol":1170:1171 */
dup3
/* "contracts/taylor_v2_tay.sol":1234:1247 */
return
/* "contracts/taylor_v2_tay.sol":1189:1191 */
tag_5:
/* "contracts/taylor_v2_tay.sol":1291:1292 */
0x01
/* "contracts/taylor_v2_tay.sol":1280:1289 */
dup2
/* "contracts/taylor_v2_tay.sol":1276:1293 */
add
/* "contracts/taylor_v2_tay.sol":1170:1171 */
dup3
/* "contracts/taylor_v2_tay.sol":1266:1294 */
mstore
pop
/* "contracts/taylor_v2_tay.sol":1746:1750 */
0xa0
/* "contracts/taylor_v2_tay.sol":1725:1744 */
dup5
/* "contracts/taylor_v2_tay.sol":1721:1751 */
add
/* "contracts/taylor_v2_tay.sol":1874:1893 */
dup5
/* "contracts/taylor_v2_tay.sol":1857:1872 */
dup2
/* "contracts/taylor_v2_tay.sol":1850:1894 */
mstore
/* "contracts/taylor_v2_tay.sol":2007:2015 */
dup4
/* "contracts/taylor_v2_tay.sol":1979:2005 */
0xc0
/* "contracts/taylor_v2_tay.sol":1725:1744 */
dup7
/* "contracts/taylor_v2_tay.sol":1979:2005 */
add
/* "contracts/taylor_v2_tay.sol":1972:2016 */
mstore
/* "contracts/taylor_v2_tay.sol":2068:2075 */
dup3
/* "contracts/taylor_v2_tay.sol":2040:2066 */
0xe0
/* "contracts/taylor_v2_tay.sol":1725:1744 */
dup7
/* "contracts/taylor_v2_tay.sol":2040:2066 */
add
/* "contracts/taylor_v2_tay.sol":2033:2076 */
mstore
/* "contracts/taylor_v2_tay.sol":2164:2179 */
dup1
/* "contracts/taylor_v2_tay.sol":950:954 */
dup7
/* "contracts/taylor_v2_tay.sol":2151:2180 */
mstore
pop
/* "contracts/taylor_v2_tay.sol":2464:2470 */
tag_6
tag_2
jump // in
tag_6:
/* "contracts/taylor_v2_tay.sol":950:954 */
dup5
/* "contracts/taylor_v2_tay.sol":2603:2614 */
mload
/* "contracts/taylor_v2_tay.sol":2710:2748 */
mload
/* "contracts/taylor_v2_tay.sol":2677:2748 */
swap4
pop
/* "contracts/taylor_v2_tay.sol":3055:3080 */
dup4
/* "contracts/taylor_v2_tay.sol":950:954 */
dup6
/* "contracts/taylor_v2_tay.sol":3042:3081 */
mstore
/* "contracts/taylor_v2_tay.sol":1170:1171 */
dup1
/* "contracts/taylor_v2_tay.sol":3316:3320 */
0x60
/* "contracts/taylor_v2_tay.sol":3289:3314 */
dup6
/* "contracts/taylor_v2_tay.sol":3285:3321 */
add
/* "contracts/taylor_v2_tay.sol":3278:3331 */
mstore
/* "contracts/taylor_v2_tay.sol":1170:1171 */
dup1
/* "contracts/taylor_v2_tay.sol":3386:3390 */
0x80
/* "contracts/taylor_v2_tay.sol":3359:3384 */
dup6
/* "contracts/taylor_v2_tay.sol":3355:3391 */
add
/* "contracts/taylor_v2_tay.sol":3348:3404 */
mstore
pop
pop
pop
pop
pop
/* "contracts/taylor_v2_tay.sol":836:3431 */
tag_4:
jump // out
tag_3:
}
solc --optimize --optimize-runs=200 -o build --bin --ast-json --asm contracts/recursive_test_wrap.sol
- with Yul code wrapped in a Solidity contract, in the
fallback
function.
608060405234801561001057600080fd5b5060c18061001f6000396000f3fe6080604052348015600f57600080fd5b506000611020526000611040526110006020526028602c565b6089565b602080518181015160408201516000805160a9811115604c578082528582f35b600101815260a0840184815260c0850184905260e085018390528552606e602c565b84515193508385528060608501528060808501525050505050565b00fea26469706673582212204a97976767bb51b7d75b1a59cc55a38a63ce7595a31ea94f0b5e78dbe00a3e4f64736f6c63430007000033
/* "contracts/taylor_recursion.sol":263:3592 contract RecursionStackOverflow {... */
mstore(0x40, 0x80)
callvalue
dup1
iszero
tag_1
jumpi
0x00
dup1
revert
tag_1:
pop
dataSize(sub_0)
dup1
dataOffset(sub_0)
0x00
codecopy
0x00
return
stop
sub_0: assembly {
/* "contracts/taylor_recursion.sol":263:3592 contract RecursionStackOverflow {... */
mstore(0x40, 0x80)
callvalue
dup1
iszero
tag_3
jumpi
0x00
dup1
revert
tag_3:
pop
/* "contracts/taylor_recursion.sol":633:634 0 */
0x00
/* "contracts/taylor_recursion.sol":602:631 add(currentMemoryFrame, 0x20) */
0x1020
/* "contracts/taylor_recursion.sol":595:635 mstore(add(currentMemoryFrame, 0x20), 0) */
mstore
/* "contracts/taylor_recursion.sol":633:634 0 */
0x00
/* "contracts/taylor_recursion.sol":669:698 add(currentMemoryFrame, 0x40) */
0x1040
/* "contracts/taylor_recursion.sol":662:702 mstore(add(currentMemoryFrame, 0x40), 0) */
mstore
/* "contracts/taylor_recursion.sol":382:386 4096 */
0x1000
/* "contracts/taylor_recursion.sol":626:630 0x20 */
0x20
/* "contracts/taylor_recursion.sol":762:794 mstore(0x20, currentMemoryFrame) */
mstore
/* "contracts/taylor_recursion.sol":804:810 eval() */
tag_6
tag_7
jump // in
tag_6:
/* "contracts/taylor_recursion.sol":979:3578 function eval() {... */
jump(tag_8)
tag_7:
/* "contracts/taylor_recursion.sol":1106:1110 0x20 */
0x20
dup1
/* "contracts/taylor_recursion.sol":1100:1111 mload(0x20) */
mload
/* "contracts/taylor_recursion.sol":1106:1110 0x20 */
dup2
/* "contracts/taylor_recursion.sol":1189:1208 _currentMemoryFrame */
dup2
/* "contracts/taylor_recursion.sol":1185:1215 add(_currentMemoryFrame, 0x20) */
add
/* "contracts/taylor_recursion.sol":1179:1216 mload(add(_currentMemoryFrame, 0x20)) */
mload
/* "contracts/taylor_recursion.sol":1279:1283 0x40 */
0x40
/* "contracts/taylor_recursion.sol":1258:1277 _currentMemoryFrame */
dup3
/* "contracts/taylor_recursion.sol":1254:1284 add(_currentMemoryFrame, 0x40) */
add
/* "contracts/taylor_recursion.sol":1248:1285 mload(add(_currentMemoryFrame, 0x40)) */
mload
/* "contracts/taylor_recursion.sol":1326:1327 0 */
0x00
dup1
/* "contracts/taylor_recursion.sol":1320:1328 mload(0) */
mload
/* "contracts/taylor_recursion.sol":1362:1365 169 */
0xa9
/* "contracts/taylor_recursion.sol":1351:1360 stepcount */
dup2
/* "contracts/taylor_recursion.sol":1348:1366 gt(stepcount, 169) */
gt
/* "contracts/taylor_recursion.sol":1345:1347 if */
iszero
tag_10
jumpi
/* "contracts/taylor_recursion.sol":1379:1388 stepcount */
dup1
/* "contracts/taylor_recursion.sol":1326:1327 0 */
dup3
/* "contracts/taylor_recursion.sol":1369:1389 mstore(0, stepcount) */
mstore
/* "contracts/taylor_recursion.sol":1106:1110 0x20 */
dup6
/* "contracts/taylor_recursion.sol":1326:1327 0 */
dup3
/* "contracts/taylor_recursion.sol":1390:1403 return(0, 32) */
return
/* "contracts/taylor_recursion.sol":1345:1347 if */
tag_10:
/* "contracts/taylor_recursion.sol":1447:1448 1 */
0x01
/* "contracts/taylor_recursion.sol":1432:1449 add(stepcount, 1) */
add
/* "contracts/taylor_recursion.sol":1422:1450 mstore(0, add(stepcount, 1)) */
dup2
mstore
/* "contracts/taylor_recursion.sol":1902:1906 0xa0 */
0xa0
/* "contracts/taylor_recursion.sol":1877:1907 add(_currentMemoryFrame, 0xa0) */
dup5
add
/* "contracts/taylor_recursion.sol":2006:2050 mstore(nextMemoryFrame, _currentMemoryFrame) */
dup5
dup2
mstore
/* "contracts/taylor_recursion.sol":2135:2161 add(nextMemoryFrame, 0x20) */
0xc0
dup6
add
/* "contracts/taylor_recursion.sol":2128:2172 mstore(add(nextMemoryFrame, 0x20), data_ptr) */
dup5
swap1
mstore
/* "contracts/taylor_recursion.sol":2196:2222 add(nextMemoryFrame, 0x40) */
0xe0
dup6
add
/* "contracts/taylor_recursion.sol":2189:2232 mstore(add(nextMemoryFrame, 0x40), env_ptr) */
dup4
swap1
mstore
/* "contracts/taylor_recursion.sol":2307:2336 mstore(0x20, nextMemoryFrame) */
dup6
mstore
/* "contracts/taylor_recursion.sol":2617:2623 eval() */
tag_11
tag_7
jump // in
tag_11:
/* "contracts/taylor_recursion.sol":1106:1110 0x20 */
dup5
/* "contracts/taylor_recursion.sol":2753:2764 mload(0x20) */
mload
/* "contracts/taylor_recursion.sol":2860:2898 mload(recursiveCallMemoryFramePointer) */
mload
/* "contracts/taylor_recursion.sol":2827:2898 let currentMemoryFramePointer := mload(recursiveCallMemoryFramePointer) */
swap4
pop
/* "contracts/taylor_recursion.sol":3205:3230 currentMemoryFramePointer */
dup4
/* "contracts/taylor_recursion.sol":1106:1110 0x20 */
dup6
/* "contracts/taylor_recursion.sol":3192:3231 mstore(0x20, currentMemoryFramePointer) */
mstore
/* "contracts/taylor_recursion.sol":1326:1327 0 */
dup1
/* "contracts/taylor_recursion.sol":3466:3470 0x60 */
0x60
/* "contracts/taylor_recursion.sol":3439:3464 currentMemoryFramePointer */
dup6
/* "contracts/taylor_recursion.sol":3435:3471 add(currentMemoryFramePointer, 0x60) */
add
/* "contracts/taylor_recursion.sol":3428:3481 mstore(add(currentMemoryFramePointer, 0x60), end_ptr) */
mstore
/* "contracts/taylor_recursion.sol":1326:1327 0 */
dup1
/* "contracts/taylor_recursion.sol":3536:3540 0x80 */
0x80
/* "contracts/taylor_recursion.sol":3509:3534 currentMemoryFramePointer */
dup6
/* "contracts/taylor_recursion.sol":3505:3541 add(currentMemoryFramePointer, 0x80) */
add
/* "contracts/taylor_recursion.sol":3498:3554 mstore(add(currentMemoryFramePointer, 0x80), result_ptr) */
mstore
pop
pop
pop
pop
pop
/* "contracts/taylor_recursion.sol":995:3578 {... */
jump // out
tag_8:
/* "contracts/taylor_recursion.sol":263:3592 contract RecursionStackOverflow {... */
stop
auxdata: 0xa26469706673582212204a97976767bb51b7d75b1a59cc55a38a63ce7595a31ea94f0b5e78dbe00a3e4f64736f6c63430007000033
}