Skip to content

Simplify recursive subroutine calling in TEAL 5 #110

@jasonpaulos

Description

@jasonpaulos

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:

# 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions