-
Notifications
You must be signed in to change notification settings - Fork 27
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
Bytecode round 12 #219
Bytecode round 12 #219
Conversation
… defaults in BytecodeGenerator
👋 Welcome back asotona! A progress list of the required criteria for merging this PR into |
@asotona This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been no new commits pushed to the ➡️ To integrate this PR with the above commit message to the |
Webrevs
|
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.
It's good to see the value-based isDominatedBy
method being used, probably the first time in anger (not much prior testing on that).
How fastidious are you being about round trip stability? Given the following chain of events, where B == bytecode and M == code mode,
B1 -> M1 -> B2 -> M2 -> B3 -> M3
Do you want M1, M2 and M3 to be the same or just M2 and M3?
} | ||
} catch (Throwable t) { | ||
error("first lift", t); | ||
} | ||
} | ||
} | ||
|
||
private void verify(String category, CoreOp.FuncOp func) { | ||
OpWriter.CodeItemNamerOption naming = null; | ||
for (Body body : func.bodies()) { |
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.
It's possible to collapse all the loops by traversing the operations, passing in naming
as the argument e.g.,
naming = func.traverse(naming, opVisitor((n, op) -> {
for (Value v : op.operands()) {
if (!op.result().isDominatedBy(v)) {
if (n == null) { n = ... }
...
}
}
return 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.
Yes, I'll change it. Original idea was that more verifications will hook also on Body and Block level, however Op-level verification seem to be enough.
} | ||
} | ||
|
||
Set<Block> stopBlocks = new HashSet<>(); |
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.
If i got this right, we are dealing with an equivalent of an uninitialized variable, of a primitive type, in source that will be assigned in divergent code paths, and the compiler will check it's DA. In the model we currently require the modelling var op be initialized with a default constant value for the primitive type. We don't currently have a concept of an uninitialized value (which might help?), so we have to detect that all loads are dominated by stores, some of which are initializing stores (which detects the uninitialized case or a redundant initialization).
This specific bit of code after the dominated by checks determines if there is a path from n
's declaring block to the entry block that does not pass through any of dom
's declaring blocks? Why is that is important?
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, this is about uninitialized variables (of any type), where there are two or more assignments in divergent paths. Concept of uninitialized variable will definitely help to avoid obsolete default constants declaration. This is a detection of such situation, so the default initialization is skipped and the roundtrip is stable.
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 code detect there is no path from a VarLoadOp to the entry block that bypass the VarStoreOps. The default initialization is necessary if there is such path. I've just made it more abstract as a dominance test of a dominant set.
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 still don't understand why the second check is needed, traversing the predecessors of n
's declaring block. If n
is not dominated by any values in set doms
then presumably such traversal has to encounter the entry block?
The implementation of Value::isDominatedBy
defers to block domination:
public boolean isDominatedBy(Value dom) {
if (this == dom) {
return true;
}
if (declaringBlock() != dom.declaringBlock()) {
return declaringBlock().isDominatedBy(dom.declaringBlock());
}
...
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.
Individual dominations are handled as you describe. It applies for cases like this:
entry -> var -> var store -> var load
\
> var store -> var load
However following case needs a test for dominant set:
entry -> var -> var store -> var load
\ /
> var store
The var load is not dominated by any individual var store, however it is dominated by them.
The test should fail for example on following case:
entry -> var -> var store -> var load
\ /
---------
The reason why the second part is focused on blocks only is because ops cannot form a bypass inside one 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.
Thanks! Doh, of course, i get it now. I think what you are doing is the equivalent of DA analysis. Perhaps we can rename the method accordingly? e.g., isDefinitelyAssigned
(noting it does not distinguish between assignment with a default value or non-assignment in source because we don't currently make that distinction in the model).
What does the bytecode verifier do when it encounters bytecode in the last case? I expect the verifier will be enhanced to support the new object initialization protocol.
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.
Code with redundant default value assignment is valid, it just causes instability (the code grows with each round).
Actually I'm testing only M2 vs M3. The first round is affected by strong stack use in the original code and BytecodeLift reflects that. BytecodeGenerator is not capable of complex predictive stack manipulations, it is limited to the one known value on stack. Second lift is then very different from the first one, however subsequent lifts should be stable. |
Thank you for the review! |
Going to push as commit 445a57f. |
All the above is fixed and the roundtrip stability is restored.
Please review.
Thanks,
Adam
Progress
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/babylon.git pull/219/head:pull/219
$ git checkout pull/219
Update a local copy of the PR:
$ git checkout pull/219
$ git pull https://git.openjdk.org/babylon.git pull/219/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 219
View PR using the GUI difftool:
$ git pr show -t 219
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/babylon/pull/219.diff
Webrev
Link to Webrev Comment