-
Notifications
You must be signed in to change notification settings - Fork 132
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
loop #95
loop #95
Conversation
This reverts commit a0d67cc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, nested loops and continue/break seem to be working as expected! My primary concern is about the sorting function, which I explain more below.
return "(While {} {})".format(self.cond, self.doBlock) | ||
|
||
def type_of(self): | ||
if self.doBlock is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type_of
is meant to indicate which type, if any, the expression pushes to the stack when it finishes, so I think this should return TealType.none
instead.
return self.doBlock.type_of() | ||
|
||
def Do(self, doBlock: Seq): | ||
self.doBlock = doBlock |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want the doBlock to end with additional things on the stack (e.g. While(cond).Do(Int(1))
should be invalid), so could you add the check require_type(doBlock.type_of(), TealType.none)
here?
Also, could you change the type annotation for doBlock
from Seq
to Expr
? I expect most of the time a Seq
will be used, but other expressions can be used as well.
pyteal/compiler/sort.py
Outdated
if i == 0: | ||
S.insert(0, m) | ||
else: | ||
if type(n) == TealConditionalBlock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for the extra complexity here? I tested locally and this simpler version worked just as well, plus it is nearly identical to the old code generation when loops are not present:
S = [start]
order = []
visited = set() # I changed visited to a set to be more efficient
while len(S) != 0:
n = S.pop()
if id(n) in visited:
continue
S += n.getOutgoing()
order.append(n)
visited.add(id(n))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ The previous version should be good here, even if For
, While
introduce cycles to the graph...
|
||
order.append(n) | ||
visited.append(n) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I realized there is a potential bug when a non-topological sort is used for this function. If there is a "diamond" graph structure, i.e. a diverging branch which later converges, like so:
start
/ \
true false
\ /
end
then one potential ordering is [start, true, end, false]
, which translates to the TEAL code:
start:
...
bz false
true:
...
end:
...
false:
...
b end
This is a problem because if end
does not contain a return
op, the ops from false
will be executed after end
finishes.
To fix this, sortBlocks
should take an additional argument, end
, which is the ending node of the graph. Then, after sorting the blocks into the list order
, we need to make the end
block the last element in the list. This guarantees no code can accidentally execute after it.
Adding this code before the return statement should do it:
endIndex = -1
for i, block in enumerate(order):
if end is block:
endIndex = i
break
if endIndex == -1:
raise TealInternalError("End block not present")
order.pop(endIndex)
order.append(end)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're correct. I simplified the algorithm as suggested.
pyteal/ir/tealsimpleblock.py
Outdated
|
||
def __eq__(self, other: object) -> bool: | ||
# check for loop | ||
if self in self.blocks: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love that these functions need to modify the object in order to work, but I can't think of a better solution right now -- I might make a follow up PR if I can think of something better.
In any case, self.blocks
and self.visited
do not need to be lists, since they only ever contain self
or nothing. A cleaner solution would be to make a single self.visited
boolean attribute, and the functions can set this to true/false to indicate whether self
has been visited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, just had some small questions and comments - I think we might want to run some formatter (e.g. black
) before pushing to make spacing/line breaks more consistent and delete any commented out code blocks.
pyteal/ast/for_.py
Outdated
|
||
def type_of(self): | ||
if str(self.doBlock) == str(Seq([Int(0)])): | ||
raise TealCompileError("For expression must have a thenBranch", self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a test for this branch? Also is this just checking if the doBlock
is False
or 0
(and if so do we just want to check for None
here)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it should be checking for None.
examples/signature/split.teal
Outdated
&& | ||
b l3 | ||
l2: | ||
bz l2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these auto-generated from the pyteal file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, it seems Shiqi updated split.teal
and dutch_auction.teal
with new generated version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I left some minor comments here and there for now.
pyteal/compiler/sort.py
Outdated
if i == 0: | ||
S.insert(0, m) | ||
else: | ||
if type(n) == TealConditionalBlock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ The previous version should be good here, even if For
, While
introduce cycles to the graph...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new formatting is much easier to read -- just one question about passing variables within CompileOptions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After these are addressed, this looks good to merge
expr = While(items[0]).Do(items[1]) | ||
actual, _ = expr.__teal__(options) | ||
|
||
options.currentLoop = expr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code from this line downward does not seem to be needed, since it doesn't check anything new.
A more appropriate test would probably be to set options.currentLoop = expr
, then do start, _ = items[1].__teal__(options)
and verify options.breakBlocks
contains only start
expr = While(items[0]).Do(items[1]) | ||
actual, _ = expr.__teal__(options) | ||
|
||
options.currentLoop = expr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above comment applies here too.
pyteal/ast/for_.py
Outdated
|
||
for block in options.continueBlocks: | ||
block.setNextBlock(stepStart) | ||
doEnd.setNextBlock(block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont't think this line should be here?
pyteal/ast/for_.py
Outdated
condEnd.setNextBlock(branchBlock) | ||
condStart.addIncoming(doStart) | ||
|
||
startEnd.nextBlock = condStart |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
condStart.addIncoming(doStart)
seems unnecessary here, since compileTeal
invokes addIncoming
on the start block of the entire AST.
Also, can you use setNextBlock()
instead of directly setting startEnd.nextBlock
?
pyteal/ast/for_.py
Outdated
|
||
def type_of(self): | ||
if self.doBlock is None: | ||
raise TealCompileError("For expression must have a thenBranch", self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: some places refer to the doBlock as thenBranch, can you update these?
expr.__teal__(options) | ||
|
||
|
||
def test_while(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this test is really good, and this file could use 2 more versions of it which test continue and break.
pyteal/compiler/compiler_test.py
Outdated
Seq( | ||
[ | ||
For( | ||
i.store(Int(0)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be j.store(Int(0))
pyteal/compiler/compiler_test.py
Outdated
i.store(Int(0)), | ||
j.load() < Int(4), | ||
j.store(j.load() + Int(2)), | ||
).Do(Seq([j.store(Int(1))])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you change the body to App.globalPut(Itob(j.load()), j.load() * Int(2))
or similar so this would not be an infinite loop?
pyteal/compiler/sort.py
Outdated
|
||
endIndex = -1 | ||
for i, block in enumerate(order): | ||
if block == end: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check if block is end
instead? This is a slightly stronger check, since we want to make sure we find the end block exactly, not a different block which happens to have the same ops as the end block, which is what ==
checks.
pyteal/ir/tealsimpleblock.py
Outdated
@@ -10,6 +10,8 @@ class TealSimpleBlock(TealBlock): | |||
def __init__(self, ops: List[TealOp]) -> None: | |||
super().__init__(ops) | |||
self.nextBlock: Optional[TealBlock] = None | |||
self.blocks: List[TealBlock] = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This variable isn't needed anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be good to go
This PR adds While and For loops to Pyteal.