Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use cover and uncover to simplify recursive subroutines #114

Merged
merged 8 commits into from
Sep 24, 2021

Conversation

jasonpaulos
Copy link
Contributor

@jasonpaulos jasonpaulos commented Sep 13, 2021

This PR uses the new cover and uncover TEAL 5 opcodes to make recursive subroutine calling more efficient.

For example, consider the following subroutine:

@Subroutine(TealType.uint64)
def multiplyByAdding(a, b):
    return (
        If(a == Int(0))
        .Then(Return(Int(0)))
        .Else(Return(b + multiplyByAdding(a - Int(1), b)))
    )

With TEAL 4, the following code will be generated (comments added by me):

sub0: // multiplyByAdding
store 1
store 0
load 0
int 0
==
bnz sub0_l2
load 1
load 0
int 1
-
load 1 # At this point, the top of the stack contains the 2 arguments for the recursive call
load 0 # <-- Here, local scratch slots 0 and 1 are spilled to the stack so they don't get overwritten in the recursive call
load 1 # <-/
dig 3 # <-- These two lines use dig to pull the 2 arguments back to the top of the stack
dig 3 # <-/
callsub sub0 # the recursive subroutine call
store 0 # the return value is placed in slot 0 temporarily
store 1 # local slot 1 is restored
load 0 # the return value is placed back on the stack
swap # the return value and the previously spilled value for slot 0 are swapped
store 0 # local slot 0 is restored
swap # <-- Because we used dig to pull the arguments above the spilled slots, the original arguments are
pop  # <--| still there. These 4 lines get rid of them and make sure the return value ends at the top of the stack.
swap # <--|
pop  # <--/
+
retsub
sub0_l2:
int 0
retsub

With TEAL 5, now the following code will be generated:

sub0: // multiplyByAdding
store 1
store 0
load 0
int 0
==
bnz sub0_l2
load 1
load 0
int 1
-
load 1 # At this point, the top of the stack contains the 2 arguments for the recursive call
load 0 # <-- Here, local scratch slots 0 and 1 are spilled to the stack so they don't get overwritten in the recursive call
load 1 # <-/
uncover 3 # <-- Now, uncover is used to pull the 2 arguments to the top of the stack
uncover 3 # <-/
callsub sub0 # the recursive subroutine call
cover 2 # the return value is pushed below the 2 spilled slots on the stack
store 1 # <-- The spilled slots are restored
store 0 # <-/
+
retsub
sub0_l2:
int 0
retsub

The TEAL 5 version is much easier to understand and more efficient in terms of program size and stack usage.

Closes #110

@jasonpaulos jasonpaulos force-pushed the recursive-subroutine-cover-uncover branch from 2d7e551 to 92e8c0d Compare September 16, 2021 21:24
@jasonpaulos jasonpaulos requested a review from ahangsu September 22, 2021 22:49
Copy link
Contributor

@ahangsu ahangsu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Should be good to go.

@jasonpaulos jasonpaulos merged commit e085d2f into master Sep 24, 2021
@jasonpaulos jasonpaulos deleted the recursive-subroutine-cover-uncover branch September 24, 2021 18:41
This was referenced Sep 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Simplify recursive subroutine calling in TEAL 5
2 participants