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

Only havoc abstract value args when really necessary #2184

Closed
wants to merge 2 commits into from

Conversation

sebmarkbage
Copy link
Contributor

Fixes #2179

In #2179 I describe why we might need to havoc some args to abstract values sometimes. This adds an invariant to ensure that we always have args in the cases we need to havoc.

If this abstract value cannot be an object, then we don't need to havoc since there's nothing that can be mutated.

If this has a known values domain, then we know the possible objects. We only need to havoc those, not the args.

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 left a comment in #2179. I don't fully understand the general reasoning here.

I feel that a one-size-fits-all treatment of abstract values doesn't generally work (unless this is just meant as an incremental refinement).

let knownObj = {x:1};
let knownObj2 = {y:3};
let conditionalObj = c ? knownObj : knownObj2;
fn(conditionalObj); // This must havoc both known objects
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this is the case where we have concrete objects in the value domain, and the second half of visitAbstractValue kicks in.

function test(fn) {
let knownObj = {x:1};
let conditionalUnknownObj = c ? knownObj : unknownObj;
fn(conditionalUnknownObj); // This must havoc the known obj
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess here the first half of visitAbstractValue, since we don't have an overapproximation of a concrete values domain. As far as I can read, this would also potentially havoc objects referenced in c. So I think another refinement and test case is needed to avoid and test that.

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 could add a special case for conditionals like this but there is really a large number of similar operations. I can’t special case them all. I just thought of this one as a test case.

@sebmarkbage
Copy link
Contributor Author

sebmarkbage commented Jun 29, 2018

This PR is just slightly better than what came before it. The additions are the proper way to model it I think.

Havocing the args (which was already there) is a bit sketchy. The goal is to be as conservative as possible for things we haven’t yet modeled so that we don’t yield incorrect code.

For loops I want to add a template object form which doesn’t guarantee which concrete instance it is but guarantees that it is not one of the known ones. I believe that can help us model abstract objects without going to topVal in more cases so that we don’t end up needing to be conservative.

"Havoced unknown object requires havocable arguments"
);

for (let i = 0, n = val.args.length; i < n; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe leave a TODO here that havcoing all args is still overly conservative in general (e.g. for "conditional").

In facebookarchive#2179 I describe why we might need to havoc some args to abstract values
sometimes. This adds an invariant to ensure that we always have args in
the cases we need to havoc.

If this abstract value cannot be an object, then we don't need to havoc
since there's nothing that can be mutated.

If this has a known values domain, then we know the possible objects. We
only need to havoc those, not the args.
for (let i = 0, n = val.args.length; i < n; i++) {
this.visitValue(val.args[i]);
if (!val.mightBeObject()) {
// Only objects need to be havoced.
Copy link
Contributor

Choose a reason for hiding this comment

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

I can see that we should not waste time on non objects nor havoc objects that appear in the val.args, but cannot be seen by the callee. This is all good.

if (val.values.isTop()) {
// If we don't know which object instances it might be,
// then it might be one of the arguments that created
// this value. See #2179.
Copy link
Contributor

Choose a reason for hiding this comment

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

Here I get concerned. By definition ALL objects known to prepack might be denoted by val. Just havocing the arguments does not seem sufficient. As things are today, we refuse to compile any program that accesses a property of an abstract object with a values domain that is Top.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don’t refuse to compile programs in pure scope mode. It havocs the object in case it had a getter on it.

It would be way too restrictive to completely not allow for these.

In practice this almost always works out though. Most operations either havoc eagerly as the operation produces the abstract, or it leaves the possible values on the args.

I think we need to come up with a less conservative way of modeling the cases that go to topVal.

// that can be havoced, we require at least one argument.
let whitelistedKind =
val.kind &&
(val.kind === "widened numeric property" || // TODO: Widened properties needs to be havocable.
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems wrong to for a value that calls out "numeric" in its kind to not have a types domain of NumberValue. Not the fault of this PR, but now that we've noticed....

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 the property name that is numeric. Not the value of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, very misleading.

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 think the widened slots will need to change to be modeled on the property binding/environment binding instead of as a value.

let whitelistedKind =
val.kind &&
(val.kind === "widened numeric property" || // TODO: Widened properties needs to be havocable.
val.kind.startsWith("abstractCounted"));
Copy link
Contributor

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 convince myself that this is OK. Essentially, anything created this way is "pre-havoced", i.e. we don't know the value of a property of such an object and we get a new one every time we access the property. At the very least we should provide and efficient and much more obviously correct way of checking that an abstract value is of this kind.

Copy link
Contributor

Choose a reason for hiding this comment

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

On second thoughts: We can only access properties of abstract objects with templates and such object will have a values domain that is not top.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Optimized functions get these arguments passed into them. https://github.com/facebook/prepack/blob/3deb5e2885a689e7b8be5bb8ad88e24fe8e4fecf/src/serializer/functions.js#L150

These two are the only cases of empty args that occurs in test coverage.

It is ok not to havoc them since they're arguments to pure functions, they can't be mutated.

@hermanventer
Copy link
Contributor

It seems important to make sure that we have 100% code coverage for the new code.

@hermanventer
Copy link
Contributor

Except for the last new test case, it is not obvious to me that the new test cases will fail on master.

@sebmarkbage
Copy link
Contributor Author

Note that master is more conservative than this. The other new test cases doesn't fail on master but they do test that the havocing that was already in master still works. If I removed the havocing of args, then there wouldn't be other tests that failed. So this ensures that all branches are covered.

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.

@sebmarkbage is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

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.

4 participants