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

Tools : PyTeal optimization utility #247

Merged
merged 75 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from 71 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
94a482b
Optimization added for repeated int constants under 2**7 w/ tests
algoidurovic Oct 14, 2021
e0795fc
fixed type problem and formatted
algoidurovic Oct 14, 2021
bb1eb20
Expanded test and added comment for clarification
algoidurovic Oct 15, 2021
6fde8ec
implement optimization utility with simple slot store/load canceling
algoidurovic Mar 16, 2022
a9ba3ef
minor refactor
algoidurovic Mar 22, 2022
73ad2a9
reformat code
algoidurovic Mar 22, 2022
b41f727
Update pyteal/compiler/optimizer/optimizer.py
algoidurovic Mar 23, 2022
0925456
Update pyteal/compiler/optimizer/optimizer.py
algoidurovic Mar 23, 2022
edbbfd2
Adding exponentiation to arithmatic ops docs (#134)
barnjamin Oct 25, 2021
4fdb032
updating to use new syntax for seq (#135)
barnjamin Oct 28, 2021
9ad3288
Make pylance recognize wildcard imports (#133)
barnjamin Nov 5, 2021
ac14058
Include pyi files in build (#137)
jasonpaulos Nov 8, 2021
4f97035
Revert "Optimization for constant assembly (#128)"
jasonpaulos Nov 8, 2021
22b7eb3
Revert "String optimization and addition of Suffix() (#126)"
jasonpaulos Nov 8, 2021
46e2a42
Update to v0.9.1 (#138)
jasonpaulos Nov 9, 2021
edfae0d
Revert "Revert "String optimization and addition of Suffix() (#126)""
jasonpaulos Nov 9, 2021
422b28e
Revert "Revert "Optimization for constant assembly (#128)""
jasonpaulos Nov 9, 2021
7c92bc7
Update examples.rst (#140)
edwardgaudio Nov 27, 2021
1999f01
Fix type for App.globalGetEx in docs (#142)
jasonpaulos Dec 7, 2021
39710b5
up max teal version (#146)
barnjamin Dec 20, 2021
2f62030
Formatting subroutines with name and newline (#148)
barnjamin Dec 28, 2021
0a445ed
Call type_of() in require_type() for better exception messages (#151)
joe-p Dec 30, 2021
1d71c44
`method` pseudo-op support for ABI methods (#153)
ahangsu Jan 3, 2022
7d0453c
Print diff of `__init__.pyi` (#166)
jasonpaulos Jan 19, 2022
49fbf04
C2C Feature Support (#149)
ahangsu Jan 19, 2022
3227438
Add BytesSqrt (#163)
StylishTriangles Jan 19, 2022
85cffeb
adding new globals from teal6 (#168)
barnjamin Jan 20, 2022
b6b2439
Acct params get (#165)
barnjamin Jan 21, 2022
8ccc2fa
Change Subroutine Wrapped Callable to a class with call method (#171)
ahangsu Jan 27, 2022
aae4548
Subroutine Type Annotations (#182)
tzaffi Feb 2, 2022
c3e5659
fix docs referencing what apps should eval to (#191)
barnjamin Feb 9, 2022
c139e93
Move from Travis to Github Actions (#190)
algojack Feb 10, 2022
2d3769b
MultiValue expression implemented to support opcodes that return mult…
algoidurovic Feb 17, 2022
2a82718
Support TEAL 6 txn fields LastLog, StateProofPK and opcodes divw, itx…
barnjamin Feb 17, 2022
a979363
Fixed typo (#202)
gconnect Feb 18, 2022
3fdb4a8
Add Github action to generate docset (#201)
aldur Feb 18, 2022
a30ff01
Update docs to group transaction field tables like go-algorand (#204)
michaeldiamant Feb 23, 2022
727f3d3
Update accessing_transaction_field.rst to fix typo (#207)
michaeldiamant Feb 24, 2022
23dbbc6
Add docs README to explain docs/ testing procedure (#205)
michaeldiamant Feb 24, 2022
d4ec5de
v0.10.0 (#206)
jasonpaulos Feb 24, 2022
bc403cc
fixing github actions to run on tags (#208)
algojack Feb 24, 2022
91e5118
Fix typos in docstrings and error messages (#211)
michaeldiamant Feb 28, 2022
b336506
Test on Python 3.10 (#212)
jasonpaulos Feb 28, 2022
df08d3f
Update versions.rst (#210)
PabloLION Feb 28, 2022
e1c4bfa
Pass-by-Ref / Dynamic Scratch Variables via the `loads` and `stores` …
tzaffi Mar 1, 2022
0cf27fb
Fix build script invocation (#223)
jasonpaulos Mar 3, 2022
76af624
Bring #225 to master (#227)
michaeldiamant Mar 3, 2022
002a480
Ignore tests generating TEAL file outputs used for expected compariso…
michaeldiamant Mar 3, 2022
bbbf5d9
Fix typo in CONTRIBUTING.md (#229)
michaeldiamant Mar 3, 2022
d3080d0
Fix subroutine mutual recursion with different argument counts bug (#…
jasonpaulos Mar 7, 2022
ff8dd95
Revert "Pass-by-Ref / Dynamic Scratch Variables via the `loads` and `…
jasonpaulos Mar 7, 2022
221e6fd
v0.10.1 (#237)
jasonpaulos Mar 7, 2022
edbae57
Revert "Revert "Pass-by-Ref / Dynamic Scratch Variables via the `load…
jasonpaulos Mar 7, 2022
c2331f9
Update user guide docs to reflect addition of DynamicScratchVar (#226)
michaeldiamant Mar 9, 2022
49f4a5c
Update CONTRIBUTING.md on PEP 8 naming conventions policy (#241)
michaeldiamant Mar 15, 2022
155f69a
implement optimization utility with simple slot store/load canceling
algoidurovic Mar 16, 2022
a521b33
minor refactor
algoidurovic Mar 22, 2022
19b9f30
reformat code
algoidurovic Mar 22, 2022
5b2fa88
correct import format to match convention
algoidurovic Mar 23, 2022
7cee3f2
resolve merge conflicts
algoidurovic Mar 23, 2022
1b04cbb
slot optimization awareness of reserved ids added
algoidurovic Mar 23, 2022
9d4b173
fix typo
algoidurovic Mar 23, 2022
3cb76ec
remove dataclass usage
algoidurovic Mar 23, 2022
4a32b3f
slight reorg of compiler process in order to perform optimization on cfg
algoidurovic Mar 28, 2022
53f6231
clean up imports
algoidurovic Mar 28, 2022
3d3208e
Merge branch 'master' of https://github.com/algorand/pyteal into opti…
algoidurovic Mar 28, 2022
b0704e6
Merge branch 'master' of https://github.com/algorand/pyteal into opti…
algoidurovic Mar 29, 2022
4b3ccf2
updated documentation and reformatted with new version of black
algoidurovic Mar 29, 2022
f353330
remove unused imports and comments
algoidurovic Mar 29, 2022
fd1f71f
reformatting
algoidurovic Mar 29, 2022
17543ed
add additional optimizer unit tests
algoidurovic Mar 29, 2022
33256a7
improve testing and slight refactoring
algoidurovic Mar 30, 2022
6795234
more renaming
algoidurovic Mar 30, 2022
89f4100
documentation and import changes
algoidurovic Mar 31, 2022
808768f
fixed typos in docs
algoidurovic Mar 31, 2022
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
61 changes: 48 additions & 13 deletions pyteal/compiler/compiler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import List, Tuple, Set, Dict, Optional, cast

from pyteal.compiler.optimizer import OptimizeOptions
from .optimizer import optimizer

from ..types import TealType
from ..ast import (
Expr,
Expand All @@ -13,7 +16,10 @@

from .sort import sortBlocks
from .flatten import flattenBlocks, flattenSubroutines
from .scratchslots import assignScratchSlotsToSubroutines
from .scratchslots import (
assignScratchSlotsToSubroutines,
collectUnoptimizedSlotIDs,
)
from .subroutines import (
spillLocalSlotsDuringRecursion,
resolveSubroutines,
Expand All @@ -31,9 +37,11 @@ def __init__(
*,
mode: Mode = Mode.Signature,
version: int = DEFAULT_TEAL_VERSION,
optimize: OptimizeOptions = None,
) -> None:
self.mode = mode
self.version = version
self.optimize = optimize if optimize is not None else OptimizeOptions()

self.currentSubroutine: Optional[SubroutineDefinition] = None

Expand Down Expand Up @@ -109,9 +117,9 @@ def verifyOpsForMode(teal: List[TealComponent], mode: Mode):
def compileSubroutine(
ast: Expr,
options: CompileOptions,
subroutineMapping: Dict[Optional[SubroutineDefinition], List[TealComponent]],
subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]],
subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock],
subroutineEndBlocks: Dict[Optional[SubroutineDefinition], TealBlock],
) -> None:
currentSubroutine = (
cast(SubroutineDeclaration, ast).subroutine
Expand Down Expand Up @@ -139,8 +147,8 @@ def compileSubroutine(
verifyOpsForVersion(teal, options.version)
verifyOpsForMode(teal, options.mode)

subroutineMapping[currentSubroutine] = teal
subroutineBlocks[currentSubroutine] = start
subroutineEndBlocks[currentSubroutine] = end

referencedSubroutines: Set[SubroutineDefinition] = set()
for stmt in teal:
Expand All @@ -150,23 +158,38 @@ def compileSubroutine(
if currentSubroutine is not None:
subroutineGraph[currentSubroutine] = referencedSubroutines

newSubroutines = referencedSubroutines - subroutineMapping.keys()
newSubroutines = referencedSubroutines - subroutineBlocks.keys()
for subroutine in sorted(newSubroutines, key=lambda subroutine: subroutine.id):
compileSubroutine(
subroutine.getDeclaration(),
options,
subroutineMapping,
subroutineGraph,
subroutineBlocks,
subroutineEndBlocks,
)


def createTealMapping(
subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock],
subroutineEndBlocks: Dict[Optional[SubroutineDefinition], TealBlock],
) -> Dict[Optional[SubroutineDefinition], List[TealComponent]]:
subroutineMapping: Dict[
Optional[SubroutineDefinition], List[TealComponent]
] = dict()
for subroutine, start in subroutineBlocks.items():
order = sortBlocks(start, subroutineEndBlocks[subroutine])
subroutineMapping[subroutine] = flattenBlocks(order)

return subroutineMapping


def compileTeal(
ast: Expr,
mode: Mode,
*,
version: int = DEFAULT_TEAL_VERSION,
assembleConstants: bool = False,
optimize: OptimizeOptions = None,
) -> str:
"""Compile a PyTeal expression into TEAL assembly.

Expand All @@ -181,6 +204,7 @@ def compileTeal(
constants will be assembled in the most space-efficient way, so enabling this may reduce
the compiled program's size. Enabling this option requires a minimum TEAL version of 3.
Defaults to false.
optimize (optional): OptimizeOptions that determine which optimization will be applied.

Returns:
A TEAL assembly program compiled from the input expression.
Expand All @@ -199,20 +223,31 @@ def compileTeal(
)
)

options = CompileOptions(mode=mode, version=version)
options = CompileOptions(mode=mode, version=version, optimize=optimize)

subroutineMapping: Dict[
Optional[SubroutineDefinition], List[TealComponent]
] = dict()
subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]] = dict()
subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock] = dict()
subroutineEndBlocks: Dict[Optional[SubroutineDefinition], TealBlock] = dict()
compileSubroutine(
ast, options, subroutineMapping, subroutineGraph, subroutineBlocks
ast, options, subroutineGraph, subroutineBlocks, subroutineEndBlocks
)

localSlotAssignments = assignScratchSlotsToSubroutines(
subroutineMapping, subroutineBlocks
)
# note: optimizations are off by default, in which case, apply_global_optimizations
# won't make any changes. Because the optimizer is invoked on a subroutine's
# control flow graph, the optimizer requires context across block boundaries. This
# is necessary for the dependency checking of local slots. Global slots, slots
# used by DynamicScratchVar, and reserved slots are not optimized.
if options.optimize.scratch_slots:
options.optimize.skip_ids = collectUnoptimizedSlotIDs(subroutineBlocks)
print(str(options.optimize.skip_ids))
for start in subroutineBlocks.values():
optimizer.apply_global_optimizations(start, options.optimize)

localSlotAssignments = assignScratchSlotsToSubroutines(subroutineBlocks)

subroutineMapping: Dict[
Optional[SubroutineDefinition], List[TealComponent]
] = createTealMapping(subroutineBlocks, subroutineEndBlocks)

spillLocalSlotsDuringRecursion(
version, subroutineMapping, subroutineGraph, localSlotAssignments
Expand Down
1 change: 1 addition & 0 deletions pyteal/compiler/optimizer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .optimizer import OptimizeOptions, apply_global_optimizations
89 changes: 89 additions & 0 deletions pyteal/compiler/optimizer/optimizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from typing import cast, Set
from pyteal.ir.tealblock import TealBlock

from pyteal.ir.tealop import TealOp
from pyteal.ir.ops import Op


class OptimizeOptions:
"""An object which specifies the optimizations to be performed and relevant context.
Args:

scratch_slots (optional): cancel contiguous store/load operations
that have no load dependencies elsewhere.
skip_ids (optional): the slot ids that should be skipped during
optimization. At the moment this includes:
1. reserved slots because they may have dependencies outside
the current application. For example, the 'gloads' opcode can
access the slots of other applications in the tx group.
2. global slots because they're outside the scope of global
optimizations, which only apply to the control flow graph of
a single subroutine.
3. slots used with dynamic scratch vars. These slots use
indirection by means of the 'stores' opcode and dependencies
can only be determined at runtime."""

def __init__(self, *, scratch_slots: bool = False, skip_ids: Set[int] = None):
self.scratch_slots = scratch_slots
self.skip_ids: Set[int] = skip_ids if skip_ids is not None else set()


def _remove_extraneous_slot_access(start: TealBlock, remove: Set[int]):
def keep_op(op: TealOp):
if type(op) != TealOp or (op.op != Op.store and op.op != Op.load):
return True

return cast(int, op.args[0]) not in remove

for block in TealBlock.Iterate(start):
block.ops = list(filter(keep_op, block.ops))


def _has_load_dependencies(cur_block: TealBlock, start: TealBlock, slot: int, pos: int):
for block in TealBlock.Iterate(start):
for i, op in enumerate(block.ops):
if block == cur_block and i == pos:
continue

if type(op) == TealOp and op.op == Op.load and op.args[0] == slot:
return True

return False


def _apply_slot_to_stack(cur_block: TealBlock, start: TealBlock, skip_ids: Set[int]):
slots_to_remove = set()
for i, op in enumerate(cur_block.ops[:-1]):
if type(op) != TealOp or op.op != Op.store:
continue

# do not optimize away reserved and global slots
if op.getSlots()[0].id in skip_ids:
continue

next_op = cur_block.ops[i + 1]
if type(next_op) != TealOp or next_op.op != Op.load:
continue

if op.args[0] != next_op.args[0]:
continue

if not _has_load_dependencies(cur_block, start, cast(int, op.args[0]), i + 1):
slots_to_remove.add(cast(int, op.args[0]))

_remove_extraneous_slot_access(start, slots_to_remove)


def apply_global_optimizations(start: TealBlock, options: OptimizeOptions) -> TealBlock:
# limit number of iterations to length of teal program to avoid potential
# infinite loops.
for block in TealBlock.Iterate(start):
prev_ops = block.ops.copy()
for _ in range(len(block.ops)):
if options.scratch_slots:
_apply_slot_to_stack(block, start, options.skip_ids)

if prev_ops == block.ops:
break

return start
Loading