Skip to content

Fix compilation of nested boolean operations#1225

Merged
coolreader18 merged 1 commit into
masterfrom
coolreader18/nested-boolops
Aug 11, 2019
Merged

Fix compilation of nested boolean operations#1225
coolreader18 merged 1 commit into
masterfrom
coolreader18/nested-boolops

Conversation

@coolreader18
Copy link
Copy Markdown
Member

I ran into this in unittest/runner.py, which has this code:

(run, run != 1 and "s" or "", timeTaken)

It was duplicating the False from the != expression but not popping it off the stack, so when BuildTuple was called it included that original False as the first element of the tuple.

Comment thread compiler/src/compile.rs
self.emit(Instruction::Pop);
}
if let Some(false_label) = false_label {
self.emit(Instruction::Duplicate);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add the test case as you mentioned in the pull request comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This compile_test is fairly complex. It implements short circuit evaluation.

Please check carefully the logic of this function. I believe the EvalContext::Expression is for the case where an expression on the stack is desired.

It is probably a good idea to expand on our snippets here, so that we cover all cases.

Comment thread compiler/src/compile.rs
@@ -1303,11 +1303,9 @@ impl<O: OutputStream> Compiler<O> {
self.emit(Instruction::Pop);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would expect the same change to be required for this line. Still I do not fully grasp the problem.

@windelbouwman
Copy link
Copy Markdown
Contributor

Suggested test snippet:

run = 1
timeTaken = 33
r = (22, run, run != 1 and "s" or "", timeTaken)
assert r == (22, 1, '', 33)

On rustpython, this now gives: (1, False, '', 33).

@coolreader18
Copy link
Copy Markdown
Member Author

coolreader18 commented Aug 11, 2019

Alright, it's taken me a while to fully grasp the issue, but I think I've got it.

Usually, when and short-circuits, it leaves a value on the stack, because it doesn't need to think about what's on the rhs. However, when and and is the lhs of an or, the or gives the short-circuit label to be the beginning of the rhs of the or, which doesn't expect a value to be on the stack, because when or doesn't short-circuit it pops the value off the stack before moving to the rhs (wrong, I only just fully understood it while typing this out). Basically, in that weird case we jump straight from the lhs of the and to the rhs of the or, and the rhs doesn't expect the lhs to leave a value on the stack. It's hard to tell just in compile_test whether or not we should pop a value off the stack before leaving the lhs of an and, because if we we're jumping from the lhs of the and to the end of the subexpression it should leave a value on the stack, but when we're jumping to the rhs of the or it shouldn't.

@coolreader18
Copy link
Copy Markdown
Member Author

Do you have any ideas for how to tackle this @windelbouwman? Maybe a flag in EvalContext::Expression?

@windelbouwman
Copy link
Copy Markdown
Contributor

windelbouwman commented Aug 11, 2019

No, I don't think an extra flag is a good option. Basically, the switch on EvalContext is the flag. In the case of EvalContext::Statement, there must be no expression left on the stack, in the case of EvalContext::Expression, there must be a single value on the stack, which caused the True or False result.

I do not have a good solution for this, except that I would try to first think of all the different test cases, and really write them out. Only then we might get the logic right, if we cover all combinations. I think it's about 8 different combinations, so we should do this first.

@windelbouwman
Copy link
Copy Markdown
Contributor

Btw, the longer I look at this compile_test function, the more issues I see.

For example:

If you set both true_label and false_label, in the EvalContext::Statement case, the stack is potentially popped twice.

@windelbouwman
Copy link
Copy Markdown
Contributor

I can have a look at it if you like? I looked at the CPython sourcecode, I think we can get ideas from there. I'll check micropython as well how they did it.

@cthulahoops
Copy link
Copy Markdown
Collaborator

Sorry about leaving this mess. I got stuck on these bugs and decided to take a break and come back to it (which never happened).

@windelbouwman
Copy link
Copy Markdown
Contributor

@cthulahoops no problem! All will be fine :).

@windelbouwman
Copy link
Copy Markdown
Contributor

Okay, I got a pretty good idea about how to solve this. Will open another pull request.

@coolreader18
Copy link
Copy Markdown
Member Author

I'll close this, then. :)

@coolreader18 coolreader18 merged commit 3364b74 into master Aug 11, 2019
@windelbouwman
Copy link
Copy Markdown
Contributor

@coolreader18 did you intend to merge this? Or close this pull request?

@coolreader18
Copy link
Copy Markdown
Member Author

Whoops, I meant to close it. I'll undo it.

@coolreader18 coolreader18 deleted the coolreader18/nested-boolops branch August 11, 2019 17:30
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.

3 participants