Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Compose without tail duplication #2460

Closed
wants to merge 1 commit into from
Closed

Compose without tail duplication #2460

wants to merge 1 commit into from

Conversation

hermanventer
Copy link
Contributor

@hermanventer hermanventer commented Aug 22, 2018

Release note: none

Closes #2435
Closes #1829

Join.composeWithEffects composes a forked completion with subsequent effects. When two or more forks could end normally, this could result in shallow copies of the subsequent effects. These were then joined together and applied, so it was mostly OK. The generator of the subsequent effects, however, ended up being joined with itself and thus transformed the generator tree to a DAG, which is not desirable for the serializer.

The new approach is to extract a join condition from the forked completion and using it to join the subsequent effects with a newly constructed empty effects. The condition ensures that the subsequent effects are applied only in situations where the forked completion is not abrupt.

Extracting this condition makes for complicated abstract expressions and this uncovered some existing bugs and limitations that are also addressed in this pull request. As a side effect, path conditions are now longer and the time to compile unrolled loops with conditional abrupt completions inside their bodies has gone up so much that the unroll limit had to be lowered.

Please note that the expected output React tests has changed because of re-ordering. I'm none too sure that this re-ordering is necessarily benign, so please review carefully.

@trueadm
Copy link
Contributor

trueadm commented Aug 22, 2018

Looking into the tests more, I thought that the ordering here mattered but it doesn't seem to matter as this is the order of how the React Reconciler takes conditions – which seems to have flipper from before so I think this is actually fine. Unfortunately, the same pushing false invariant still occurs in our internal React bundle and also from #2361 (I wasn't sure if this PR was to also fix or somewhat address those issues too).

It appears that test/serializer/optimized-functions/LoopBailout18.js is causing the CI to fail because it takes longer than 10m and thus CircleCI fails the tests.

https://support.circleci.com/hc/en-us/articles/360007188574-Build-has-hit-timeout-limit

@@ -284,7 +285,8 @@ export function computeBinary(

if (isPure && effects) {
realm.applyEffects(effects);
return realm.returnOrThrowCompletion(effects.result);
if (effects.result instanceof SimpleNormalCompletion) return effects.result.value;
effects.result;
Copy link
Contributor

Choose a reason for hiding this comment

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

return effects.result;?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually no, that is debugging code that got left in. I'll remove.

@hermanventer
Copy link
Contributor Author

hermanventer commented Aug 22, 2018

@trueadm: The issue in #2361 does not reproduce for me at all. Oh wait, I assumed it was a serializer test case. I'll see what I can do using debug-fb-www.js.

@trueadm
Copy link
Contributor

trueadm commented Aug 22, 2018

@hermanventer Sorry, this only seems to occur with abstractValueImpliesMax set to 1000, as per debug-fb-www's settings.

Copy link
Contributor

@NTillmann NTillmann left a comment

Choose a reason for hiding this comment

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

I feel like the simplifier rules are becoming quite problematic to write and rewrite. We need a more principled approach. Maybe not Z3 based, but there should be some kind of term rewriting system that we should be able to leverage, to write and verify rules and to generate code from it.
And instead of poking around narrow patterns, there should be normal forms, and efficient ways to encode the boolean structures...

joinValuesOfSelectedCompletions(
selector: Completion => boolean,
completion: Completion,
keepInfeasiblePaths: boolean = false
Copy link
Contributor

Choose a reason for hiding this comment

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

Why would it ever be desirable to keep infeasible paths?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is not desirable, but a hack to work around the fact that generator.emitConditionalThrow wants a value for its parameter. Fixing that to take the completion instead would be preferable. For this PR, however, that seems out of scope.

@@ -597,7 +597,7 @@ export class Generator {
} else if (result instanceof JoinedNormalAndAbruptCompletions) {
let selector = c =>
c instanceof ThrowCompletion && c.value !== realm.intrinsics.__bottomValue && !(c.value instanceof EmptyValue);
output.emitConditionalThrow(Join.joinValuesOfSelectedCompletions(selector, result));
output.emitConditionalThrow(Join.joinValuesOfSelectedCompletions(selector, result, true));
Copy link
Contributor

Choose a reason for hiding this comment

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

It's hard to tell from the callsite what the , true argument means. Maybe make it named?

Why don't you want to keepInfeasiblePaths here?

if (y instanceof AbstractValue && y.kind === "||") {
// x || x || y <=> x || y
if (x.equals(y.args[0])) return y;
// y || x || y <=> x || y
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment seems to be missing code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll delete the comment.

if (x.equals(y.args[0])) return y;
// y || x || y <=> x || y
if (x instanceof AbstractValue && x.kind === "!") {
// !x || x || y <=> y
Copy link
Contributor

Choose a reason for hiding this comment

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

This (and the next) rule seems wrong, unless I am missing some special JavaScript craziness.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The comment is correct if you don't equate the meta variable x with the comment variable x. I'll update the comment to make it more difficult to equate them.

// y && (x && y) <=> x && y
if (x.equals(y.args[0]) || x.equals(y.args[1])) return y;
}
// x && (x && y || x && z) <=> x && (y && z)
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment seems wrong, while the implementation may be correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The comment tries to communicate the pattern in the most abstract way. The code does not follow the names in the comment when things get complicated.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the comment should be // x && (x && y || x && z) <=> x && (y || z)

Copy link
Contributor Author

@hermanventer hermanventer left a comment

Choose a reason for hiding this comment

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

At this stage, rewriting the simplifier does not seem like a top priority. It certainly is out of scope for this PR.

if (y instanceof AbstractValue && y.kind === "||") {
// x || x || y <=> x || y
if (x.equals(y.args[0])) return y;
// y || x || y <=> x || y
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll delete the comment.

if (x.equals(y.args[0])) return y;
// y || x || y <=> x || y
if (x instanceof AbstractValue && x.kind === "!") {
// !x || x || y <=> y
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The comment is correct if you don't equate the meta variable x with the comment variable x. I'll update the comment to make it more difficult to equate them.

// y && (x && y) <=> x && y
if (x.equals(y.args[0]) || x.equals(y.args[1])) return y;
}
// x && (x && y || x && z) <=> x && (y && z)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The comment tries to communicate the pattern in the most abstract way. The code does not follow the names in the comment when things get complicated.

// y && (x && y) <=> x && y
if (x.equals(y.args[0]) || x.equals(y.args[1])) return y;
}
// x && (x && y || x && z) <=> x && (y && z)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the comment should be // x && (x && y || x && z) <=> x && (y || z)

if (x.equals(y.args[0])) return y;
if (x instanceof AbstractValue && x.kind === "!") {
// !x0 || x0 || y1 <=> y1
if (x.args[0].equals(y.args[0])) return y.args[1];
Copy link
Contributor

Choose a reason for hiding this comment

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

This still seems wrong to me. !x || x || y should be equivalent to true, not y.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Duh! I'll remove this.

Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

hermanventer has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

hermanventer has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

// x || x || y <=> x || y
if (x.equals(y.args[0])) return y;
if (x instanceof AbstractValue && x.kind === "!") {
// !x0 || y0 || x0 <=> y0
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems wrong to me. !x0 || ... || x0 is true, not equivalent to some random value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct. Moreover, only if isCondition is true.

if (x.args[0].equals(y) || x.args[1].equals(y)) return x;
} else if (x.kind === "!") {
// !x && x <=> false
if (x.args[0].equals(y)) return realm.intrinsics.false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Only if isCondition, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct.

@hermanventer
Copy link
Contributor Author

hermanventer commented Aug 24, 2018

@trueadm Sadly, the internal React test is failing, presumably because of exceeding implies limits (a lot of those appear in the log output). Could you have a look and give a prognosis of how fixable this may be?

It might be necessary to make some changes so that simplification has a better worst case complexity. (And that may not be easy at all.)

Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

hermanventer has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

if (x.kind === "&&") {
// (x && y) && x <=> x && y
// (x && y) && y <=> x && y
if (x.args[0].equals(y) || x.args[1].equals(y)) return x;
Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking about it, the first one is actually only valid if isCondition without reordering: (1 && 2) && 1 vs 1 && 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch.

@trueadm
Copy link
Contributor

trueadm commented Aug 24, 2018

This PR is currently failing the internal React bundle test with an invariant in _getTarget in the serializer. I'll dig into it today and see if I can see if there's anything obvious.

facebook-github-bot pushed a commit that referenced this pull request Aug 29, 2018
Summary:
Release note: Speed up simplifier by using an implication cache per path branch

The realm's path conditions is now a class instances and an explicit tree, along with caches for expressions that have already been checked for true/false using Path.implies on the current set of path conditions.

The AbstractValueImplicationCounter is still there as flag, to be renamed later. It is no longer used as a global k-limit, but enables k-limits on the cost of constructing a new set of path conditions. When React is the target, path conditions are not re-specialized. This leads to a very nice performance win for the large React internal test.

A number of tweaks to the simplifier were needed to get tests to pass. Some of these were borrowed from PR #2460.
Pull Request resolved: #2494

Reviewed By: trueadm

Differential Revision: D9554820

Pulled By: hermanventer

fbshipit-source-id: 5fdc550499975fe11c0c954b9502cd4eeab2bafe
facebook-github-bot pushed a commit that referenced this pull request Aug 31, 2018
Summary:
Release note: none

This fixes two problems uncovered while debugging the failure of PR #2460 when run on a large internal test case:
1) CSE did not fix up recently introduced aliases of expressions.
2) The serializer logic visited array elements that were not visited because of recently introduced leak logic.
Pull Request resolved: #2516

Differential Revision: D9599235

Pulled By: hermanventer

fbshipit-source-id: a2047227b4aa2e0e2def7d8e7ebe0724a9525160
Copy link

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

hermanventer has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

Copy link
Contributor

@trueadm trueadm left a comment

Choose a reason for hiding this comment

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

LGTM, merging as this is blocker for others.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants