-
Notifications
You must be signed in to change notification settings - Fork 425
Nested abstract conditional fixes #2255
Nested abstract conditional fixes #2255
Conversation
Nice work, but it seems that we have a race condition here. See PR #2250. If you rebase on top of that, it would be easier to do a detailed review. |
src/methods/join.js
Outdated
}; | ||
|
||
let cloneEffects = () => { | ||
// TODO (hermanv) if we use e as is, it ends up being applied twice when the join of the normal |
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.
@hermanventer: is this reuse of your TODO
logic ok?
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'm currently working on this code, so there will be merge conflicts for one of us.
src/values/AbstractObjectValue.js
Outdated
skipInvariant: true, | ||
isPure: true, | ||
} | ||
{ skipInvariant: true } |
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.
@trueadm I noticed that some property accesses were not emitted in my NestedConditions
test and found your discussion in:
Getters could have a “pure” side-effect like console.log
, correct? If a function call cannot be removed in pure scope, then a getter probably shouldn’t be as well?
This change is only tangentially related. I thought my serializer tests would snapshot the serialized output, but they don’t. Happy to remove if you disagree or move to another PR.
Here’s a gist with the serialized output without this “fix”:
https://gist.github.com/calebmer/3ba090cda321ce67576c8d3c73ec5670
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.
console.log
is technically a side-effectful thing right? Why were you using getters for in your test? Minifiers like google closure compiler will also strip these too, so we added the same assumptions to our wiki doc. Let's talk about this in a separate issue – I don't want to side track the work you've done in this PR :)
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 for the context. I thought this was a bug in my code 👍
c5c198a
to
3d7e72e
Compare
Rebased onto #2250. Here are the two cases this diff fixes before and after. The first is fixed by passing effects to The second is fixed by refactoring Inputfunction fn1(arg) {
if (arg.foo) {
if (arg.bar) {
return 42;
}
}
}
function fn2(arg) {
if (arg.foo) {
return 1;
} else {
if (arg.bar) {
return 2;
}
}
}
__optimize(fn1);
__optimize(fn2); Bad Ouputvar _1 = function (arg) {
return _$1 ? 42 : void 0; // <------------- `_$1` is not defined
};
var _6 = function (arg) {
var _$2 = arg.foo;
if (!_$2) {
var _$3 = arg.bar;
if (!_$3) {
var _$3 = arg.bar; // <------------- `arg.bar` is defined twice, could also be `arg.bar()`
}
}
return _$2 ? 1 : _$3 ? 2 : void 0;
}; After #2250var _1 = function (arg) {
var _$0 = arg.foo;
return _$0 ? _$1 ? 42 : void 0 : void 0; // <------------- `_$1` is still not defined
};
var _8 = function (arg) {
var _$2 = arg.foo;
if (!_$2) {
var _$3 = arg.bar;
if (!_$3) {
var _$3 = arg.bar; // <------------- `arg.bar` is defined twice, could also be `arg.bar()`
}
}
return _$2 ? 1 : _$3 ? 2 : void 0;
}; After this diffvar _1 = function (arg) {
var _$0 = arg.foo;
if (_$0) {
var _$1 = arg.bar;
}
return _$0 ? _$1 ? 42 : void 0 : void 0;
};
var _8 = function (arg) {
var _$2 = arg.foo;
if (!_$2) {
var _$3 = arg.bar;
}
return _$2 ? 1 : _$3 ? 2 : void 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.
Looks great.
src/methods/join.js
Outdated
}; | ||
|
||
let cloneEffects = () => { | ||
// TODO (hermanv) if we use e as is, it ends up being applied twice when the join of the normal |
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'm currently working on this code, so there will be merge conflicts for one of us.
src/methods/join.js
Outdated
let ae = pnc.alternateEffects; | ||
let nae = new Effects(na, ae.generator, ae.modifiedBindings, ae.modifiedProperties, ae.createdObjects); | ||
invariant(pnca instanceof PossiblyNormalCompletion); | ||
let [na, nae] = recurse(pnca, pnc.alternateEffects); |
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 took me a while to figure out that recurse captures and e and sneaks it into nae. I would make it more explicit if it were up to me.
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.
👍
return [nx, nxe]; | ||
}; | ||
|
||
let cloneEffects = () => { |
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 would prefer a function, rather than a procedure with side effects.
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 removed the side effects. Since you’ll be working on this code, I’ll let you move it to the appropriate scope 👍
3d7e72e
to
bc39bb4
Compare
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 great, just a few CI things to sort out :)
bc39bb4
to
8fb1788
Compare
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.
@calebmer is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
70a2027
to
cc0c3bc
Compare
cc0c3bc
to
d73d560
Compare
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.
@calebmer is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
Awesome work! I'm seeing this output on master: (function () {
var _$2 = this;
var _1 = function (arg) {
var _$0 = arg.foo;
if (_$0) {
var _$1 = arg.bar;
}
if (_$0) {
var _$1 = arg.bar;
}
return _$0 ? _$1 ? 42 : void 0 : void 0;
};
var _0 = function () {
return JSON.stringify([_1(null), _1({}), _1({
foo: true
})]);
};
_$2.inspect = _0;
}).call(this); Note |
Sent #2282 as a first step towards avoiding such regressions. We still need to fix them before we can merge it though. |
Summary: gaearon found a regression to the cases I fixed in #2255. The following code: ```js function fn1(arg) { if (arg.foo()) { if (arg.bar()) { return 42; } } } function fn2(arg) { if (arg.foo()) { if (arg.bar()) { return 1; } } else { return 2; } } if (global.__optimize) { __optimize(fn1); __optimize(fn2); } ``` Now compiles to: ```js var fn1, fn2; (function() { var _$4 = this; var _0 = function(arg) { var _$0 = arg.foo(); if (_$0) { var _$1 = arg.bar(); } if (_$0) { var _$1 = arg.bar(); // <----------------------------- `arg.bar()` is redeclared. } return _$0 ? (_$1 ? 42 : void 0) : void 0; }; var _8 = function(arg) { var _$2 = arg.foo(); var _$3 = arg.bar(); // <------------------------------- `arg.bar()` should be inside `if (_$2)` return _$2 ? (_$3 ? 1 : void 0) : 2; }; _$4.fn1 = _0; _$4.fn2 = _8; }.call(this)); ``` After looking through the commits, I found that #2274 regressed this case. After reverting that PR I found that these cases were fixed again, but interestingly the test added in that PR also passed after I reverted the change. I’m opening this PR with the failing tests so hermanventer can take a look. I haven’t looked too deeply into this case, so I’m probably missing something important. Maybe some race condition between the landing of #2255 and #2274? Pull Request resolved: #2288 Differential Revision: D9540424 Pulled By: calebmer fbshipit-source-id: 3ef03068cb575617d64d73fdc1f9431188effdda
Fixes #1867 and fixes #2058 which are issues blocking the React compiler.
Test case from #1867 copied below:
The fix is almost entirely in
joinOrForkResults
where I remove the call toupdatePossiblyNormalCompletionWithConditionalSimpleNormalCompletion
and replace it with a newPossiblyNormalCompletion
. Here’s my understanding of the issue and the previous strategy. Hopefully @hermanventer can correct me if I’m wrong or explain why it needs to be the way it was.In our program above when we are joining the
arg.foo
consequent with its alternate we have the following completion for thearg.foo
consequent:PossiblyNormalCompletion
(join condition isarg.bar
)ReturnCompletion(42)
(value of42
)SimpleNormalCompletion(empty)
(there is noelse
)We are joining this with an alternate of
SimpleNormalCompletion
since there is noelse
on thearg.foo
if-statement. Before we would try and “sneak into” the two branches and add an abstract conditional to the result. However, the implementation was limited so we we only updatedSimpleNormalCompletion
. This meant we ended up changing our above completion to:PossiblyNormalCompletion
(join condition isarg.bar
)ReturnCompletion(42)
SimpleNormalCompletion(arg.foo ? empty : empty)
The only change being the second
SimpleNormalCompletion
is nowarg.foo ? empty : empty
. This would serialize into roughly:Where
_$1
is not declared! Since we were serializing aPossiblyNormalCompletion
with a join condition ofarg.bar
and thearg.foo ? empty : empty
abstract conditional simplified to justempty
which means we never emitarg.foo
so never emitarg.bar
which has a dependency onarg.foo
.My strategy was to nest the
PossiblyNormalCompletion
in anotherPossiblyNormalCompletion
. So the final completion for the code sample at the top of this PR is:PossiblyNormalCompletion
(join condition isarg.foo
)PossiblyNormalCompletion
(join condition isarg.bar
)ReturnCompletion(42)
SimpleNormalCompletion(empty)
SimpleNormalCompletion(empty)
I’m confused as to why this was not done in the first place and instead an update strategy was used.
I then wrote a test (now in
test/serializer/optimized-functions/NestedConditions.js
) to enumerate a bunch of nested conditional cases. The rest of the fixes come from there. Notably the refactor ofreplacePossiblyNormalCompletionWithForkedAbruptCompletion
which did not produce the same output whenconsequent
andalternate
were flipped.