diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index 804f61871..1644e8343 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -217,7 +217,7 @@ def compileTeal( ) spillLocalSlotsDuringRecursion( - subroutineMapping, subroutineGraph, localSlotAssignments + version, subroutineMapping, subroutineGraph, localSlotAssignments ) subroutineLabels = resolveSubroutines(subroutineMapping) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index e7358857a..a9a56975e 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -816,7 +816,7 @@ def storeValue(value: Expr) -> Expr: retsub """.strip() actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual + assert actual == expected def test_compile_subroutine_with_return(): @@ -869,7 +869,7 @@ def getValue() -> Expr: retsub """.strip() actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual + assert actual == expected def test_compile_subroutine_many_args(): @@ -916,7 +916,7 @@ def calculateSum( retsub """.strip() actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual + assert actual == expected def test_compile_subroutine_recursive(): @@ -967,7 +967,154 @@ def isEven(i: Expr) -> Expr: retsub """.strip() actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual + assert actual == expected + + +def test_compile_subroutine_recursive_5(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return ( + If(i == Int(0)) + .Then(Int(1)) + .ElseIf(i == Int(1)) + .Then(Int(0)) + .Else(isEven(i - Int(2))) + ) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 5 +int 6 +callsub sub0 +return +sub0: // isEven +store 0 +load 0 +int 0 +== +bnz sub0_l5 +load 0 +int 1 +== +bnz sub0_l4 +load 0 +int 2 +- +load 0 +swap +callsub sub0 +swap +store 0 +sub0_l3: +b sub0_l6 +sub0_l4: +int 0 +b sub0_l3 +sub0_l5: +int 1 +sub0_l6: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_multiple_args(): + @Subroutine(TealType.uint64) + def multiplyByAdding(a, b): + return ( + If(a == Int(0)) + .Then(Return(Int(0))) + .Else(Return(b + multiplyByAdding(a - Int(1), b))) + ) + + program = Return(multiplyByAdding(Int(3), Int(5))) + + expected = """#pragma version 4 +int 3 +int 5 +callsub sub0 +return +sub0: // multiplyByAdding +store 1 +store 0 +load 0 +int 0 +== +bnz sub0_l2 +load 1 +load 0 +int 1 +- +load 1 +load 0 +load 1 +dig 3 +dig 3 +callsub sub0 +store 0 +store 1 +load 0 +swap +store 0 +swap +pop +swap +pop ++ +retsub +sub0_l2: +int 0 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_multiple_args_5(): + @Subroutine(TealType.uint64) + def multiplyByAdding(a, b): + return ( + If(a == Int(0)) + .Then(Return(Int(0))) + .Else(Return(b + multiplyByAdding(a - Int(1), b))) + ) + + program = Return(multiplyByAdding(Int(3), Int(5))) + + expected = """#pragma version 5 +int 3 +int 5 +callsub sub0 +return +sub0: // multiplyByAdding +store 1 +store 0 +load 0 +int 0 +== +bnz sub0_l2 +load 1 +load 0 +int 1 +- +load 1 +load 0 +load 1 +uncover 3 +uncover 3 +callsub sub0 +cover 2 +store 1 +store 0 ++ +retsub +sub0_l2: +int 0 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected def test_compile_subroutine_mutually_recursive(): @@ -1031,7 +1178,67 @@ def isOdd(i: Expr) -> Expr: retsub """.strip() actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual + assert actual == expected + + +def test_compile_subroutine_mutually_recursive_5(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) + + @Subroutine(TealType.uint64) + def isOdd(i: Expr) -> Expr: + return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 5 +int 6 +callsub sub0 +return +sub0: // isEven +store 0 +load 0 +int 0 +== +bnz sub0_l2 +load 0 +int 1 +- +load 0 +swap +callsub sub1 +swap +store 0 +! +b sub0_l3 +sub0_l2: +int 1 +sub0_l3: +retsub +sub1: // isOdd +store 1 +load 1 +int 0 +== +bnz sub1_l2 +load 1 +int 1 +- +load 1 +swap +callsub sub0 +swap +store 1 +! +b sub1_l3 +sub1_l2: +int 0 +sub1_l3: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected def test_compile_loop_in_subroutine(): @@ -1071,7 +1278,7 @@ def setState(value: Expr) -> Expr: retsub """.strip() actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual + assert actual == expected def test_compile_subroutine_assemble_constants(): @@ -1129,4 +1336,4 @@ def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: retsub """.strip() actual = compileTeal(program, Mode.Application, version=4, assembleConstants=True) - assert expected == actual + assert actual == expected diff --git a/pyteal/compiler/subroutines.py b/pyteal/compiler/subroutines.py index 496f8206e..a9f8064f5 100644 --- a/pyteal/compiler/subroutines.py +++ b/pyteal/compiler/subroutines.py @@ -62,6 +62,7 @@ def findRecursionPoints( def spillLocalSlotsDuringRecursion( + version: int, subroutineMapping: Dict[Optional[SubroutineDefinition], List[TealComponent]], subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]], localSlots: Dict[Optional[SubroutineDefinition], Set[int]], @@ -75,6 +76,7 @@ def spillLocalSlotsDuringRecursion( slots from being modifying by a new recursive invocation of the current subroutine. Args: + version: The current TEAL version being assembled. subroutineMapping: A dictionary containing a list of TealComponents for every subroutine in a program. The key None is taken to indicate the main program routine. This input may be modified by this function in order to spill subroutine slots. @@ -87,6 +89,8 @@ def spillLocalSlotsDuringRecursion( """ recursivePoints = findRecursionPoints(subroutineGraph) + coverAvailable = version >= Op.cover.min_version + for subroutine, reentryPoints in recursivePoints.items(): slots = list(sorted(slot for slot in localSlots[subroutine])) numArgs = subroutine.argumentCount() @@ -108,63 +112,89 @@ def spillLocalSlotsDuringRecursion( # restore the local slots after returning from the subroutine. This prevents a # reentry into the current subroutine from modifying variables we are currently # using. + + digArgs = True + coverSpilledSlots = False + uncoverArgs = False + if coverAvailable: + digArgs = False + if len(slots) < numArgs: + coverSpilledSlots = True + else: + uncoverArgs = True + for slot in slots: # spill local slots to the stack before.append(TealOp(None, Op.load, slot)) + if coverSpilledSlots: + # numArgs is guaranteed to be at least 2 here (since numArgs > len(slots) + # and len(slots) must be at least 1 for the code to get this far), so no + # need to replace this with swap if numArgs is 1 + before.append(TealOp(None, Op.cover, numArgs)) + for _ in range(numArgs): # pull the subroutine arguments to the top of the stack, above the just spilled - # local slots - - # 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, + # local slots, if needed + + stackDistance = len(slots) + numArgs - 1 + + if uncoverArgs: + if stackDistance == 1: + before.append(TealOp(None, Op.swap)) + else: + before.append(TealOp(None, Op.uncover, stackDistance)) + + if digArgs: + before.append( + TealOp( + None, + Op.dig, + stackDistance, + ) ) - ) - # 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 + # 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 + hideReturnValueInFirstSlot = 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 + if len(slots) == 1: + after.append(TealOp(None, Op.swap)) + elif coverAvailable: + after.append(TealOp(None, Op.cover, len(slots))) + else: + # Store the return value into slots[0] temporarily + hideReturnValueInFirstSlot = True + after.append(TealOp(None, Op.store, slots[0])) 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]: + if hideReturnValueInFirstSlot 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)) + + # 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)) + if digArgs: + 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)) newOps += before newOps.append(stmt) diff --git a/pyteal/compiler/subroutines_test.py b/pyteal/compiler/subroutines_test.py index 820e19295..6ec26f709 100644 --- a/pyteal/compiler/subroutines_test.py +++ b/pyteal/compiler/subroutines_test.py @@ -138,42 +138,116 @@ def sub3Impl(a1, a2, a3): def test_spillLocalSlotsDuringRecursion_no_subroutines(): - l1Label = LabelReference("l1") - mainOps = [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), - ] + for version in (4, 5): + l1Label = LabelReference("l1") + mainOps = [ + TealOp(None, Op.txn, "Fee"), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bz, l1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.return_), + TealLabel(None, l1Label), + TealOp(None, Op.int, 0), + TealOp(None, Op.return_), + ] - subroutineMapping = {None: mainOps} + subroutineMapping = {None: mainOps} - subroutineGraph = dict() + subroutineGraph = dict() - localSlots = {None: set()} + localSlots = {None: set()} - spillLocalSlotsDuringRecursion(subroutineMapping, subroutineGraph, localSlots) + spillLocalSlotsDuringRecursion( + version, subroutineMapping, subroutineGraph, localSlots + ) - assert mainOps == [ - TealOp(None, Op.txn, "Fee"), - TealOp(None, Op.int, 0), - TealOp(None, Op.eq), - TealOp(None, Op.bz, l1Label), - TealOp(None, Op.int, 1), - TealOp(None, Op.return_), - TealLabel(None, l1Label), - TealOp(None, Op.int, 0), - TealOp(None, Op.return_), - ] + assert mainOps == [ + TealOp(None, Op.txn, "Fee"), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bz, l1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.return_), + TealLabel(None, l1Label), + TealOp(None, Op.int, 0), + TealOp(None, Op.return_), + ] def test_spillLocalSlotsDuringRecursion_1_subroutine_no_recursion(): - subroutine = SubroutineDefinition(lambda: None, TealType.uint64) + for version in (4, 5): + subroutine = SubroutineDefinition(lambda: None, TealType.uint64) + + subroutineL1Label = LabelReference("l1") + subroutineOps = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bnz, subroutineL1Label), + TealOp(None, Op.err), + TealLabel(None, subroutineL1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.retsub), + ] + + l1Label = LabelReference("l1") + mainOps = [ + TealOp(None, Op.txn, "Fee"), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bz, l1Label), + TealOp(None, Op.int, 100), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + TealLabel(None, l1Label), + TealOp(None, Op.int, 0), + TealOp(None, Op.return_), + ] + + subroutineMapping = {None: mainOps, subroutine: subroutineOps} + + subroutineGraph = {subroutine: set()} + + localSlots = {None: set(), subroutine: {0}} + + spillLocalSlotsDuringRecursion( + version, subroutineMapping, subroutineGraph, localSlots + ) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.txn, "Fee"), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bz, l1Label), + TealOp(None, Op.int, 100), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + TealLabel(None, l1Label), + TealOp(None, Op.int, 0), + TealOp(None, Op.return_), + ], + subroutine: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bnz, subroutineL1Label), + TealOp(None, Op.err), + TealLabel(None, subroutineL1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.retsub), + ], + } + + +def test_spillLocalSlotsDuringRecursion_1_subroutine_recursion_v4(): + def sub1Impl(a1): + return None + + subroutine = SubroutineDefinition(sub1Impl, TealType.uint64) subroutineL1Label = LabelReference("l1") subroutineOps = [ @@ -182,7 +256,13 @@ def test_spillLocalSlotsDuringRecursion_1_subroutine_no_recursion(): TealOp(None, Op.int, 0), TealOp(None, Op.eq), TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.err), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 1), + TealOp(None, Op.minus), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.int, 1), + TealOp(None, Op.add), + TealOp(None, Op.retsub), TealLabel(None, subroutineL1Label), TealOp(None, Op.int, 1), TealOp(None, Op.retsub), @@ -204,11 +284,11 @@ def test_spillLocalSlotsDuringRecursion_1_subroutine_no_recursion(): subroutineMapping = {None: mainOps, subroutine: subroutineOps} - subroutineGraph = {subroutine: set()} + subroutineGraph = {subroutine: {subroutine}} localSlots = {None: set(), subroutine: {0}} - spillLocalSlotsDuringRecursion(subroutineMapping, subroutineGraph, localSlots) + spillLocalSlotsDuringRecursion(4, subroutineMapping, subroutineGraph, localSlots) assert subroutineMapping == { None: [ @@ -229,7 +309,19 @@ def test_spillLocalSlotsDuringRecursion_1_subroutine_no_recursion(): TealOp(None, Op.int, 0), TealOp(None, Op.eq), TealOp(None, Op.bnz, subroutineL1Label), - TealOp(None, Op.err), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 1), + TealOp(None, Op.minus), + TealOp(None, Op.load, 0), + TealOp(None, Op.dig, 1), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.swap), + TealOp(None, Op.store, 0), + TealOp(None, Op.swap), + TealOp(None, Op.pop), + TealOp(None, Op.int, 1), + TealOp(None, Op.add), + TealOp(None, Op.retsub), TealLabel(None, subroutineL1Label), TealOp(None, Op.int, 1), TealOp(None, Op.retsub), @@ -237,7 +329,7 @@ def test_spillLocalSlotsDuringRecursion_1_subroutine_no_recursion(): } -def test_spillLocalSlotsDuringRecursion_1_subroutine_recursion(): +def test_spillLocalSlotsDuringRecursion_1_subroutine_recursion_v5(): def sub1Impl(a1): return None @@ -282,7 +374,7 @@ def sub1Impl(a1): localSlots = {None: set(), subroutine: {0}} - spillLocalSlotsDuringRecursion(subroutineMapping, subroutineGraph, localSlots) + spillLocalSlotsDuringRecursion(5, subroutineMapping, subroutineGraph, localSlots) assert subroutineMapping == { None: [ @@ -307,12 +399,10 @@ def sub1Impl(a1): TealOp(None, Op.int, 1), TealOp(None, Op.minus), TealOp(None, Op.load, 0), - TealOp(None, Op.dig, 1), + TealOp(None, Op.swap), TealOp(None, Op.callsub, subroutine), TealOp(None, Op.swap), TealOp(None, Op.store, 0), - TealOp(None, Op.swap), - TealOp(None, Op.pop), TealOp(None, Op.int, 1), TealOp(None, Op.add), TealOp(None, Op.retsub), @@ -324,6 +414,130 @@ def sub1Impl(a1): def test_spillLocalSlotsDuringRecursion_multiple_subroutines_no_recursion(): + for version in (4, 5): + + def sub1Impl(a1): + return None + + def sub2Impl(a1, a2): + return None + + def sub3Impl(a1, a2, a3): + return None + + subroutine1 = SubroutineDefinition(sub1Impl, TealType.uint64) + subroutine2 = SubroutineDefinition(sub1Impl, TealType.uint64) + subroutine3 = SubroutineDefinition(sub1Impl, TealType.none) + + subroutine1L1Label = LabelReference("l1") + subroutine1Ops = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bnz, subroutine1L1Label), + TealOp(None, Op.err), + TealLabel(None, subroutine1L1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.callsub, subroutine3), + TealOp(None, Op.retsub), + ] + + subroutine2L1Label = LabelReference("l1") + subroutine2Ops = [ + TealOp(None, Op.store, 1), + TealOp(None, Op.load, 1), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bnz, subroutine2L1Label), + TealOp(None, Op.err), + TealLabel(None, subroutine2L1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.retsub), + ] + + subroutine3Ops = [ + TealOp(None, Op.retsub), + ] + + l1Label = LabelReference("l1") + mainOps = [ + TealOp(None, Op.txn, "Fee"), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bz, l1Label), + TealOp(None, Op.int, 100), + TealOp(None, Op.callsub, subroutine1), + TealOp(None, Op.return_), + TealLabel(None, l1Label), + TealOp(None, Op.int, 101), + TealOp(None, Op.callsub, subroutine2), + TealOp(None, Op.return_), + ] + + subroutineMapping = { + None: mainOps, + subroutine1: subroutine1Ops, + subroutine2: subroutine2Ops, + subroutine3: subroutine3Ops, + } + + subroutineGraph = { + subroutine1: {subroutine2}, + subroutine2: set(), + subroutine3: set(), + } + + localSlots = {None: set(), subroutine1: {0}, subroutine2: {1}, subroutine3: {}} + + spillLocalSlotsDuringRecursion( + version, subroutineMapping, subroutineGraph, localSlots + ) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.txn, "Fee"), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bz, l1Label), + TealOp(None, Op.int, 100), + TealOp(None, Op.callsub, subroutine1), + TealOp(None, Op.return_), + TealLabel(None, l1Label), + TealOp(None, Op.int, 101), + TealOp(None, Op.callsub, subroutine2), + TealOp(None, Op.return_), + ], + subroutine1: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bnz, subroutine1L1Label), + TealOp(None, Op.err), + TealLabel(None, subroutine1L1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.callsub, subroutine3), + TealOp(None, Op.retsub), + ], + subroutine2: [ + TealOp(None, Op.store, 1), + TealOp(None, Op.load, 1), + TealOp(None, Op.int, 0), + TealOp(None, Op.eq), + TealOp(None, Op.bnz, subroutine2L1Label), + TealOp(None, Op.err), + TealLabel(None, subroutine2L1Label), + TealOp(None, Op.int, 1), + TealOp(None, Op.retsub), + ], + subroutine3: [ + TealOp(None, Op.retsub), + ], + } + + +def test_spillLocalSlotsDuringRecursion_multiple_subroutines_recursion_v4(): def sub1Impl(a1): return None @@ -344,10 +558,15 @@ def sub3Impl(a1, a2, a3): TealOp(None, Op.int, 0), TealOp(None, Op.eq), TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine1L1Label), + TealOp(None, Op.load, 0), TealOp(None, Op.int, 1), - TealOp(None, Op.callsub, subroutine3), + TealOp(None, Op.minus), + TealOp(None, Op.callsub, subroutine1), + TealOp(None, Op.int, 1), + TealOp(None, Op.add), + TealOp(None, Op.retsub), + TealLabel(None, subroutine1L1Label), + TealOp(None, Op.load, 255), TealOp(None, Op.retsub), ] @@ -358,18 +577,27 @@ def sub3Impl(a1, a2, a3): TealOp(None, Op.int, 0), TealOp(None, Op.eq), TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.err), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 1), + TealOp(None, Op.minus), + TealOp(None, Op.callsub, subroutine1), + TealOp(None, Op.int, 1), + TealOp(None, Op.add), + TealOp(None, Op.retsub), TealLabel(None, subroutine2L1Label), TealOp(None, Op.int, 1), TealOp(None, Op.retsub), ] subroutine3Ops = [ + TealOp(None, Op.callsub, subroutine3), TealOp(None, Op.retsub), ] l1Label = LabelReference("l1") mainOps = [ + TealOp(None, Op.int, 1), + TealOp(None, Op.store, 255), TealOp(None, Op.txn, "Fee"), TealOp(None, Op.int, 0), TealOp(None, Op.eq), @@ -381,6 +609,7 @@ def sub3Impl(a1, a2, a3): TealOp(None, Op.int, 101), TealOp(None, Op.callsub, subroutine2), TealOp(None, Op.return_), + TealOp(None, Op.callsub, subroutine3), ] subroutineMapping = { @@ -391,17 +620,19 @@ def sub3Impl(a1, a2, a3): } subroutineGraph = { - subroutine1: {subroutine2}, - subroutine2: set(), - subroutine3: set(), + subroutine1: {subroutine1}, + subroutine2: {subroutine1}, + subroutine3: {subroutine3}, } localSlots = {None: set(), subroutine1: {0}, subroutine2: {1}, subroutine3: {}} - spillLocalSlotsDuringRecursion(subroutineMapping, subroutineGraph, localSlots) + spillLocalSlotsDuringRecursion(4, subroutineMapping, subroutineGraph, localSlots) assert subroutineMapping == { None: [ + TealOp(None, Op.int, 1), + TealOp(None, Op.store, 255), TealOp(None, Op.txn, "Fee"), TealOp(None, Op.int, 0), TealOp(None, Op.eq), @@ -413,6 +644,7 @@ def sub3Impl(a1, a2, a3): TealOp(None, Op.int, 101), TealOp(None, Op.callsub, subroutine2), TealOp(None, Op.return_), + TealOp(None, Op.callsub, subroutine3), ], subroutine1: [ TealOp(None, Op.store, 0), @@ -420,10 +652,21 @@ def sub3Impl(a1, a2, a3): TealOp(None, Op.int, 0), TealOp(None, Op.eq), TealOp(None, Op.bnz, subroutine1L1Label), - TealOp(None, Op.err), - TealLabel(None, subroutine1L1Label), + TealOp(None, Op.load, 0), TealOp(None, Op.int, 1), - TealOp(None, Op.callsub, subroutine3), + TealOp(None, Op.minus), + TealOp(None, Op.load, 0), + TealOp(None, Op.dig, 1), + TealOp(None, Op.callsub, subroutine1), + TealOp(None, Op.swap), + TealOp(None, Op.store, 0), + TealOp(None, Op.swap), + TealOp(None, Op.pop), + TealOp(None, Op.int, 1), + TealOp(None, Op.add), + TealOp(None, Op.retsub), + TealLabel(None, subroutine1L1Label), + TealOp(None, Op.load, 255), TealOp(None, Op.retsub), ], subroutine2: [ @@ -432,18 +675,25 @@ def sub3Impl(a1, a2, a3): TealOp(None, Op.int, 0), TealOp(None, Op.eq), TealOp(None, Op.bnz, subroutine2L1Label), - TealOp(None, Op.err), + TealOp(None, Op.load, 0), + TealOp(None, Op.int, 1), + TealOp(None, Op.minus), + TealOp(None, Op.callsub, subroutine1), + TealOp(None, Op.int, 1), + TealOp(None, Op.add), + TealOp(None, Op.retsub), TealLabel(None, subroutine2L1Label), TealOp(None, Op.int, 1), TealOp(None, Op.retsub), ], subroutine3: [ + TealOp(None, Op.callsub, subroutine3), TealOp(None, Op.retsub), ], } -def test_spillLocalSlotsDuringRecursion_multiple_subroutines_recursion(): +def test_spillLocalSlotsDuringRecursion_multiple_subroutines_recursion_v5(): def sub1Impl(a1): return None @@ -533,7 +783,7 @@ def sub3Impl(a1, a2, a3): localSlots = {None: set(), subroutine1: {0}, subroutine2: {1}, subroutine3: {}} - spillLocalSlotsDuringRecursion(subroutineMapping, subroutineGraph, localSlots) + spillLocalSlotsDuringRecursion(5, subroutineMapping, subroutineGraph, localSlots) assert subroutineMapping == { None: [ @@ -562,12 +812,10 @@ def sub3Impl(a1, a2, a3): TealOp(None, Op.int, 1), TealOp(None, Op.minus), TealOp(None, Op.load, 0), - TealOp(None, Op.dig, 1), + TealOp(None, Op.swap), TealOp(None, Op.callsub, subroutine1), TealOp(None, Op.swap), TealOp(None, Op.store, 0), - TealOp(None, Op.swap), - TealOp(None, Op.pop), TealOp(None, Op.int, 1), TealOp(None, Op.add), TealOp(None, Op.retsub), @@ -599,6 +847,436 @@ def sub3Impl(a1, a2, a3): } +def test_spillLocalSlotsDuringRecursion_recursive_many_args_no_return_v4(): + def subImpl(a1, a2, a3): + return None + + subroutine = SubroutineDefinition(subImpl, TealType.none) + + subroutineOps = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.retsub), + ] + + mainOps = [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.int, 1), + TealOp(None, Op.return_), + ] + + subroutineMapping = { + None: mainOps, + subroutine: subroutineOps, + } + + subroutineGraph = { + subroutine: {subroutine}, + } + + localSlots = {None: set(), subroutine: {0, 1, 2}} + + spillLocalSlotsDuringRecursion(4, subroutineMapping, subroutineGraph, localSlots) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.int, 1), + TealOp(None, Op.return_), + ], + subroutine: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.load, 0), + TealOp(None, Op.load, 1), + TealOp(None, Op.load, 2), + TealOp(None, Op.dig, 5), + TealOp(None, Op.dig, 5), + TealOp(None, Op.dig, 5), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.store, 2), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 0), + TealOp(None, Op.pop), + TealOp(None, Op.pop), + TealOp(None, Op.pop), + TealOp(None, Op.retsub), + ], + } + + +def test_spillLocalSlotsDuringRecursion_recursive_many_args_no_return_v5(): + def subImpl(a1, a2, a3): + return None + + subroutine = SubroutineDefinition(subImpl, TealType.none) + + subroutineOps = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.retsub), + ] + + mainOps = [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.int, 1), + TealOp(None, Op.return_), + ] + + subroutineMapping = { + None: mainOps, + subroutine: subroutineOps, + } + + subroutineGraph = { + subroutine: {subroutine}, + } + + localSlots = {None: set(), subroutine: {0, 1, 2}} + + spillLocalSlotsDuringRecursion(5, subroutineMapping, subroutineGraph, localSlots) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.int, 1), + TealOp(None, Op.return_), + ], + subroutine: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.load, 0), + TealOp(None, Op.load, 1), + TealOp(None, Op.load, 2), + TealOp(None, Op.uncover, 5), + TealOp(None, Op.uncover, 5), + TealOp(None, Op.uncover, 5), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.store, 2), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 0), + TealOp(None, Op.retsub), + ], + } + + +def test_spillLocalSlotsDuringRecursion_recursive_many_args_return_v4(): + def subImpl(a1, a2, a3): + return None + + subroutine = SubroutineDefinition(subImpl, TealType.uint64) + + subroutineOps = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.retsub), + ] + + mainOps = [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ] + + subroutineMapping = { + None: mainOps, + subroutine: subroutineOps, + } + + subroutineGraph = { + subroutine: {subroutine}, + } + + localSlots = {None: set(), subroutine: {0, 1, 2}} + + spillLocalSlotsDuringRecursion(4, subroutineMapping, subroutineGraph, localSlots) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ], + subroutine: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.load, 0), + TealOp(None, Op.load, 1), + TealOp(None, Op.load, 2), + TealOp(None, Op.dig, 5), + TealOp(None, Op.dig, 5), + TealOp(None, Op.dig, 5), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 2), + TealOp(None, Op.store, 1), + TealOp(None, Op.load, 0), + TealOp(None, Op.swap), + TealOp(None, Op.store, 0), + TealOp(None, Op.swap), + TealOp(None, Op.pop), + TealOp(None, Op.swap), + TealOp(None, Op.pop), + TealOp(None, Op.swap), + TealOp(None, Op.pop), + TealOp(None, Op.retsub), + ], + } + + +def test_spillLocalSlotsDuringRecursion_recursive_many_args_return_v5(): + def subImpl(a1, a2, a3): + return None + + subroutine = SubroutineDefinition(subImpl, TealType.uint64) + + subroutineOps = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.retsub), + ] + + mainOps = [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ] + + subroutineMapping = { + None: mainOps, + subroutine: subroutineOps, + } + + subroutineGraph = { + subroutine: {subroutine}, + } + + localSlots = {None: set(), subroutine: {0, 1, 2}} + + spillLocalSlotsDuringRecursion(5, subroutineMapping, subroutineGraph, localSlots) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ], + subroutine: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.load, 0), + TealOp(None, Op.load, 1), + TealOp(None, Op.load, 2), + TealOp(None, Op.uncover, 5), + TealOp(None, Op.uncover, 5), + TealOp(None, Op.uncover, 5), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.cover, 3), + TealOp(None, Op.store, 2), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 0), + TealOp(None, Op.retsub), + ], + } + + +def test_spillLocalSlotsDuringRecursion_recursive_more_args_than_slots_v5(): + def subImpl(a1, a2, a3): + return None + + subroutine = SubroutineDefinition(subImpl, TealType.uint64) + + subroutineOps = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.pop), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.retsub), + ] + + mainOps = [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ] + + subroutineMapping = { + None: mainOps, + subroutine: subroutineOps, + } + + subroutineGraph = { + subroutine: {subroutine}, + } + + localSlots = {None: set(), subroutine: {0, 1}} + + spillLocalSlotsDuringRecursion(5, subroutineMapping, subroutineGraph, localSlots) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ], + subroutine: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.pop), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.load, 0), + TealOp(None, Op.cover, 3), + TealOp(None, Op.load, 1), + TealOp(None, Op.cover, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.cover, 2), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 0), + TealOp(None, Op.retsub), + ], + } + + +def test_spillLocalSlotsDuringRecursion_recursive_more_slots_than_args_v5(): + def subImpl(a1, a2, a3): + return None + + subroutine = SubroutineDefinition(subImpl, TealType.uint64) + + subroutineOps = [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 10), + TealOp(None, Op.store, 3), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.retsub), + ] + + mainOps = [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ] + + subroutineMapping = { + None: mainOps, + subroutine: subroutineOps, + } + + subroutineGraph = { + subroutine: {subroutine}, + } + + localSlots = {None: set(), subroutine: {0, 1, 2, 3}} + + spillLocalSlotsDuringRecursion(5, subroutineMapping, subroutineGraph, localSlots) + + assert subroutineMapping == { + None: [ + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.return_), + ], + subroutine: [ + TealOp(None, Op.store, 0), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 2), + TealOp(None, Op.int, 10), + TealOp(None, Op.store, 3), + TealOp(None, Op.int, 1), + TealOp(None, Op.int, 2), + TealOp(None, Op.int, 3), + TealOp(None, Op.load, 0), + TealOp(None, Op.load, 1), + TealOp(None, Op.load, 2), + TealOp(None, Op.load, 3), + TealOp(None, Op.uncover, 6), + TealOp(None, Op.uncover, 6), + TealOp(None, Op.uncover, 6), + TealOp(None, Op.callsub, subroutine), + TealOp(None, Op.cover, 4), + TealOp(None, Op.store, 3), + TealOp(None, Op.store, 2), + TealOp(None, Op.store, 1), + TealOp(None, Op.store, 0), + TealOp(None, Op.retsub), + ], + } + + def test_resolveSubroutines(): def sub1Impl(a1): return None