-
Notifications
You must be signed in to change notification settings - Fork 132
Closed
Labels
Description
Summary
TEAL 5 introduces the cover and uncover opcodes for stack manipulation, which can be used to greatly reduce the amount of opcodes and stack space needed to implement recursive subroutine calling.
Scope
Instead of using dig and pop to help spill scratch slots during a potentially recursive subroutine call in spillLocalSlotsDuringRecursion, cover and uncover can do a better job. There are comments in this section of code describing the exact improvements that can be made:
pyteal/pyteal/compiler/subroutines.py
Lines 119 to 167 in 1157c57
| # TODO: TEAL 5+, do this instead: | |
| # before.append(TealOp(None, Op.uncover, len(slots))) | |
| # or just do cover during the previous loop where slots are loaded, whichever | |
| # is more efficient | |
| before.append( | |
| TealOp( | |
| None, | |
| Op.dig, | |
| len(slots) + subroutine.argumentCount() - 1, | |
| ) | |
| ) | |
| # because we are stuck using dig instead of uncover in TEAL 4, we'll need to | |
| # pop all of the dug up arguments after the function returns | |
| preserveReturnValue = False | |
| if subroutine.returnType != TealType.none: | |
| # if the subroutine returns a value on the stack, we need to preserve this after | |
| # restoring all local slots. | |
| preserveReturnValue = True | |
| if len(slots) > 1: | |
| # Store the return value into slots[0] temporarily. As an optimization, if | |
| # len(slots) == 1 we can just do a single swap instead | |
| after.append(TealOp(None, Op.store, slots[0])) | |
| # TODO: TEAL 5+, just do cover len(slots), so after restoring all slots the | |
| # return value is on top of the stack | |
| for slot in slots[::-1]: | |
| # restore slots, iterating in reverse because slots[-1] is at the top of the stack | |
| if preserveReturnValue and slot is slots[0]: | |
| # time to restore the return value to the top of the stack | |
| if len(slots) > 1: | |
| # slots[0] is being used to store the return value, so load it again | |
| after.append(TealOp(None, Op.load, slot)) | |
| # swap the return value with the actual value of slot[0] on the stack | |
| after.append(TealOp(None, Op.swap)) | |
| after.append(TealOp(None, Op.store, slot)) | |
| for _ in range(numArgs): | |
| # clear out the duplicate arguments that were dug up previously, since dig | |
| # does not pop the dug values -- once we use cover/uncover to properly set up | |
| # the spilled slots, this will no longer be necessary | |
| if subroutine.returnType != TealType.none: | |
| # if there is a return value on top of the stack, we need to preserve | |
| # it, so swap it with the subroutine argument that's below it on the | |
| # stack | |
| after.append(TealOp(None, Op.swap)) | |
| after.append(TealOp(None, Op.pop)) |
Currently
Priority
Not strictly needed, but this is a worthwhile improvement for subroutines.
barnjamin