Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion pyteal/ast/scratch.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import cast, TYPE_CHECKING

import traceback # TODO: this is temporary!!!

from pyteal.types import TealType, require_type
from pyteal.config import NUM_SLOTS
from pyteal.errors import TealInputError, TealInternalError
Expand Down Expand Up @@ -28,6 +30,30 @@ def __init__(self, requestedSlotId: int | None = None):
requestedSlotId (optional): A scratch slot id that the compiler must store the value.
This id may be a Python int in the range [0-256).
"""
# Compilation ID:
self._cid: int | None = None
# Temporary:
tb = traceback.extract_stack()

pt_indices = [i + 1 for i, b in enumerate(reversed(tb)) if "/pyteal/" in b[0]]
self.creator_tb_idx = max(pt_indices)

def back(n):
filename, line_number, function_name, _ = tb[-n]
return {
"loc": f"{filename}:{line_number}:",
"function_name": function_name,
}

self.creator = back(self.creator_tb_idx)

def breadcrumb(n):
return back(n)["loc"].split("/")[-1]

self.breadcrumbs = [
breadcrumb(i) for i in range(self.creator_tb_idx, 0, -1)
]

if requestedSlotId is None:
self.id = ScratchSlot.nextSlotId
ScratchSlot.nextSlotId += 1
Expand Down Expand Up @@ -71,7 +97,14 @@ def __repr__(self):
return "ScratchSlot({})".format(self.id)

def __str__(self):
return "slot#{}".format(self.id)
code = "?"
if (bc := self.breadcrumbs[1]) == 'router.py:1099:':
code = "C"
elif bc == 'router.py:1095:':
code = "B"
arrows = "-".join(x.split(":")[1] for x in self.breadcrumbs)
pid = hash(arrows)
return "({}{})slot#{}:{}".format(code, pid, self.id, arrows)


ScratchSlot.__module__ = "pyteal"
Expand Down
11 changes: 11 additions & 0 deletions pyteal/compiler/compiler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from dataclasses import dataclass
import threading
import time
from typing import Dict, Final, List, Optional, Set, Tuple, cast

from algosdk.v2client.algod import AlgodClient
Expand Down Expand Up @@ -297,6 +299,10 @@ def get_results(self) -> CompileResults:


class Compilation:
ready_to_unpause_lock: threading.Lock = threading.Lock()
ready_to_unpause: int = 0
paused: bool = True

def __init__(
self,
ast: Expr,
Expand Down Expand Up @@ -419,6 +425,11 @@ def _compile_impl(
)
for start in subroutine_start_blocks.values():
apply_global_optimizations(start, options.optimize, self.version)

with self.ready_to_unpause_lock:
self.ready_to_unpause += 1
while self.paused:
time.sleep(0.1)

localSlotAssignments: Dict[
Optional[SubroutineDefinition], Set[int]
Expand Down
30 changes: 27 additions & 3 deletions pyteal/compiler/scratchslots.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def collectSlotsFromBlock(block: TealBlock):


def collectScratchSlots(
subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock]
subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock],
starting_cid: int | None = None,
) -> Tuple[Set[ScratchSlot], Dict[Optional[SubroutineDefinition], Set[ScratchSlot]]]:
"""Find and return all referenced ScratchSlots for each subroutine.

Expand All @@ -53,13 +54,30 @@ def collectScratchSlots(
same as subroutineBlocks, and whose values are the local slots of that
subroutine.
"""
cid: int | None = starting_cid
assert cid is None or cid >= 0, f"negative id not allowed but {starting_cid=}"

def incrementing() -> bool:
nonlocal cid
return cid is not None

def next_cid() -> int:
nonlocal cid
if not incrementing():
return -1
curr = cid
cid += 1
return curr

subroutineSlots: Dict[Optional[SubroutineDefinition], Set[ScratchSlot]] = dict()

def collectSlotsFromBlock(block: TealBlock, slots: Set[ScratchSlot]):
for op in block.ops:
for slot in op.getSlots():
slots.add(slot)
if slot._cid is None and (nid := next_cid()) >= 0:
slot._cid = nid


for subroutine, start in subroutineBlocks.items():
slots: Set[ScratchSlot] = set()
Expand Down Expand Up @@ -107,7 +125,9 @@ def assignScratchSlotsToSubroutines(
integers representing the assigned IDs of slots which appear only in that subroutine
(subroutine local slots).
"""
global_slots, local_slots = collectScratchSlots(subroutineBlocks)
from pyteal import ScratchSlot

global_slots, local_slots = collectScratchSlots(subroutineBlocks, starting_cid=0)
# all scratch slots referenced by the program
allSlots: Set[ScratchSlot] = global_slots | cast(Set[ScratchSlot], set()).union(
*local_slots.values()
Expand Down Expand Up @@ -146,7 +166,11 @@ def assignScratchSlotsToSubroutines(
raise TealInternalError(msg) from errors[0]

nextSlotIndex = 0
for slot in sorted(allSlots, key=lambda slot: slot.id):
# sorted_slots = sorted(allSlots, key=lambda slot: slot._cid)
sorted_slots = sorted(allSlots, key=lambda slot: slot.id)
x = 42
print(f"\n{ScratchSlot.nextSlotId=}")
for slot in sorted_slots:
# Find next vacant slot that compiler can assign to
while nextSlotIndex in slotIds:
nextSlotIndex += 1
Expand Down
2 changes: 1 addition & 1 deletion pyteal/compiler/subroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def spillLocalSlotsDuringRecursion(

"Spill to stack" means loading all local slots onto the stack, invoking the subroutine which may
result in recursion, then restoring all local slots from the stack. This prevents the local
slots from being modifying by a new recursive invocation of the current subroutine.
slots from being modified by a new recursive invocation of the current subroutine.

Args:
version: The current program version being assembled.
Expand Down
1 change: 0 additions & 1 deletion pyteal/ir/tealblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class TealBlock(ABC):
"""Represents a basic block of TealComponents in a graph."""

def __init__(self, ops: List[TealOp], root_expr: Optional["Expr"] = None) -> None:
# TODO: do I still need root_expr?
self.ops = ops
self.incoming: List[TealBlock] = []
self._root_expr = root_expr
Expand Down
36 changes: 22 additions & 14 deletions tests/compile_asserts.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,35 @@ def compile_and_save(approval, version: int, test_name: str) -> tuple[Path, str,
return tealdir, name, compiled


def assert_teal_as_expected(path2actual: Path, path2expected: Path):
def assert_teal_as_expected(
path2actual: Path, path2expected: Path, skip_final_assertion=False
) -> tuple[list[str], list[str]]:
with open(path2actual, "r") as f:
actual_lines = f.readlines()

with open(path2expected, "r") as f:
expected_lines = f.readlines()

diff = list(
unified_diff(
expected_lines,
actual_lines,
fromfile=str(path2expected),
tofile=str(path2actual),
n=3,
if not skip_final_assertion:
diff = list(
unified_diff(
expected_lines,
actual_lines,
fromfile=str(path2expected),
tofile=str(path2actual),
n=3,
)
)
)

assert (
len(diff) == 0
), f"Difference between expected and actual TEAL code:\n\n{''.join(diff)}"
assert (
len(diff) == 0
), f"Difference between expected and actual TEAL code:\n\n{''.join(diff)}"
return expected_lines, actual_lines


def assert_new_v_old(approve_func, version: int, test_name: str):
def assert_new_v_old(
approve_func, version: int, test_name: str, skip_final_assertion=False
) -> tuple[list[str], list[str]]:
tealdir, name, compiled = compile_and_save(approve_func, version, test_name)

print(
Expand All @@ -58,4 +64,6 @@ def assert_new_v_old(approve_func, version: int, test_name: str):

path2actual = tealdir / (name + ".teal")
path2expected = FIXTURES / test_name / (name + ".teal")
assert_teal_as_expected(path2actual, path2expected)
return assert_teal_as_expected(
path2actual, path2expected, skip_final_assertion=skip_final_assertion
)
8 changes: 4 additions & 4 deletions tests/integration/graviton_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
GENERATED = PATH / "generated"

# TODO: remove these skips after the following issue has been fixed https://github.com/algorand/pyteal/issues/199
STABLE_SLOT_GENERATION = False
SKIP_SCRATCH_ASSERTIONS = not STABLE_SLOT_GENERATION
SKIP_SCRATCH_ASSERTIONS = True

# ---- Helper ---- #

Expand Down Expand Up @@ -134,11 +133,12 @@ def fib(n):
# ---- Blackbox pure unit tests (Skipping for now due to flakiness) ---- #


@pytest.mark.skipif(not STABLE_SLOT_GENERATION, reason="cf. #199")
ISSUE_199_CASES = [exp, square_byref, square, swap, string_mult, oldfac, slow_fibonacci]
@pytest.mark.skipif(SKIP_SCRATCH_ASSERTIONS, reason="cf. #199")
@pytest.mark.parametrize(
"subr, mode",
product(
[exp, square_byref, square, swap, string_mult, oldfac, slow_fibonacci],
ISSUE_199_CASES,
[pt.Mode.Application, pt.Mode.Signature],
),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#pragma version 6
txna ApplicationArgs 0
btoi
store 1
pushint 1 // 1
store 0
pushint 0 // 0
callsub squarebyref_0
pushint 1337 // 1337
store 0
load 0
store 1
load 1
itob
log
load 0
load 1
return

// square_byref
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
#pragma version 6
intcblock 1
txna ApplicationArgs 0
store 1
intc_0 // 1
store 0
pushint 0 // 0
txna ApplicationArgs 1
btoi
callsub stringmult_0
store 0
load 0
store 1
load 1
log
load 0
load 1
len
return

// string_mult
stringmult_0:
store 3
store 2
store 3
intc_0 // 1
store 4
load 2
load 3
loads
store 5
load 2
load 3
pushbytes 0x // ""
stores
stringmult_0_l1:
load 4
load 3
load 2
<=
bz stringmult_0_l3
load 2
load 2
load 3
load 3
loads
load 5
concat
Expand All @@ -42,6 +42,6 @@ intc_0 // 1
store 4
b stringmult_0_l1
stringmult_0_l3:
load 2
load 3
loads
retsub
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
#pragma version 6
txna ApplicationArgs 0
store 1
store 0
txna ApplicationArgs 1
store 2
store 1
pushint 0 // 0
pushint 1 // 1
pushint 2 // 2
callsub swap_0
pushint 1337 // 1337
store 0
load 0
store 2
load 2
itob
log
load 0
load 2
return

// swap
swap_0:
store 4
store 3
load 3
store 4
load 4
loads
store 5
load 3
load 4
load 3
loads
stores
load 4
load 3
load 5
stores
retsub
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
#pragma version 6
arg 0
btoi
store 1
pushint 1 // 1
store 0
pushint 0 // 0
callsub squarebyref_0
pushint 1337 // 1337
return

// square_byref
squarebyref_0:
store 0
load 0
load 0
store 1
load 1
load 1
loads
load 0
load 1
loads
*
stores
Expand Down
Loading