Bug 201994 - Inline caching is wrong for custom accessors and custom values
Summary: Inline caching is wrong for custom accessors and custom values
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Saam Barati
URL:
Keywords: InRadar
Depends on: 202594
Blocks:
  Show dependency treegraph
 
Reported: 2019-09-19 11:25 PDT by Saam Barati
Modified: 2019-10-14 08:33 PDT (History)
16 users (show)

See Also:


Attachments
WIP (23.00 KB, patch)
2019-09-19 18:55 PDT, Saam Barati
no flags Details | Formatted Diff | Diff
WIP (30.60 KB, patch)
2019-09-20 17:56 PDT, Saam Barati
no flags Details | Formatted Diff | Diff
WIP (42.63 KB, patch)
2019-09-23 18:26 PDT, Saam Barati
no flags Details | Formatted Diff | Diff
WIP (43.28 KB, patch)
2019-09-23 22:47 PDT, Saam Barati
ews-watchlist: commit-queue-
Details | Formatted Diff | Diff
patch (55.55 KB, patch)
2019-09-25 16:35 PDT, Saam Barati
no flags Details | Formatted Diff | Diff
patch (55.38 KB, patch)
2019-09-25 16:40 PDT, Saam Barati
ysuzuki: review+
Details | Formatted Diff | Diff
patch for landing (59.92 KB, patch)
2019-09-30 17:03 PDT, Saam Barati
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Saam Barati 2019-09-19 11:25:34 PDT
We just cache these without caring what happens with the prototype that holds it. So we could delete the property, and we'd still execute the IC as if the custom value/accessor were still present
Comment 1 Saam Barati 2019-09-19 14:21:40 PDT
Custom values broken:
```
function assert(b) {
    if (!b)
        throw new Error;
}
function getMultiline(o) {
    return o.multiline;
}
noInline(getMultiline);


const o = {};
o.__proto__ = RegExp;
RegExp.multiline = false;

for (let i = 0; i < 500; ++i) {
    assert(getMultiline(o) === false);
}
delete RegExp.input;
delete RegExp.multiline;
assert(getMultiline(o) === undefined); // fail here
```


Custom accessors broken:
```
function bar(e) {
    return e.nodeType;
}
let node = ...;
for (let i = 0; i < 500; ++i)
    assert(bar(node) === 1);

assert(delete Node.prototype.nodeType);

assert(bar(node) === undefined); // fail here
```
Comment 2 Saam Barati 2019-09-19 18:55:22 PDT
Created attachment 379185 [details]
WIP

it begins
Comment 3 Saam Barati 2019-09-20 17:53:28 PDT
<rdar://problem/50850326>
Comment 4 Saam Barati 2019-09-20 17:56:11 PDT
Created attachment 379297 [details]
WIP
Comment 5 Saam Barati 2019-09-23 18:26:44 PDT
Created attachment 379413 [details]
WIP

almost done. Just need to add stuff for customs we pull out of thin air (not in property storage and not in the static property table)
Comment 6 Saam Barati 2019-09-23 22:47:19 PDT
Created attachment 379431 [details]
WIP
Comment 7 EWS Watchlist 2019-09-24 01:04:16 PDT
Comment on attachment 379431 [details]
WIP

Attachment 379431 [details] did not pass jsc-ews (mac):
Output: https://webkit-queues.webkit.org/results/13062829

New failing tests:
mozilla-tests.yaml/js1_5/Array/regress-101964.js.mozilla-dfg-eager-no-cjit-validate-phases
Comment 8 Saam Barati 2019-09-25 16:35:38 PDT
Created attachment 379589 [details]
patch
Comment 9 Saam Barati 2019-09-25 16:36:39 PDT
Comment on attachment 379589 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=379589&action=review

> Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp:394
> -    return generateConditions(
> +    auto result = generateConditions(

will turn this back into a return
Comment 10 Saam Barati 2019-09-25 16:40:00 PDT
Created attachment 379590 [details]
patch
Comment 11 Saam Barati 2019-09-26 08:11:51 PDT
will add this as a test too:


```
<!DOCTYPE HTML>
<html>
	<body>
		<div id="result"></div>
		<script>
		(function() {
			"use strict";

			let setterCalled = false;

			function accessProperty() {
				let oScript = document.createElement("script");
				oScript.src = Math.random();
			}

			// Force "code optimization" by calling the function several times
			for (let i = 0; i < 1000; i++) {
				accessProperty();
			}

			// Define a custom setter for HTMLScriptElement#src
			const descriptor = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, "src");
			Object.defineProperty(HTMLScriptElement.prototype, "src", {
				get: descriptor.get,
				set: function() {

					// Switch marker once setter is called
					setterCalled = true;

					descriptor.set.apply(this, arguments);
				},
				enumerable: descriptor.enumerable,
				configurable: descriptor.configurable
			});
		 
			// Access the property once again, which should invoke the setter
			accessProperty();

			// Print result
			document.getElementById("result").innerText = "Setter called: " + setterCalled;

		})();
		</script>
	</body>
</html>

```
Comment 12 Saam Barati 2019-09-26 08:46:38 PDT
Comment on attachment 379590 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=379590&action=review

> Source/JavaScriptCore/ChangeLog:1
> +2019-09-25  Saam Barati  <sbarati@apple.com>

I'll also note in the changelog that this is neutral on my microbenchmarks
Comment 13 Mark Lam 2019-09-30 10:37:24 PDT
Comment on attachment 379590 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=379590&action=review

Some comments for now while I continue reading the patch.

> Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp:67
> +        if (!structure->propertyAccessesAreCacheable())
> +            return nullptr;
> +
> +        if (structure->isProxy())
> +            return nullptr;
> +

Can you explain why this move from above is necessary?  AFAICT, it looks like you just want to flatten dictionaries more aggressively.  Is that correct?  I suppose structure->propertyAccessesAreCacheable() will fail if the structure is for a dictionary.

> Source/JavaScriptCore/runtime/ClassInfo.h:221
>      unsigned staticClassSize;

You probably missed this field because it only showed up after a rebase.  Can you move it up with the rest as well?

> Source/JavaScriptCore/runtime/Structure.h:56
> +struct HashTable;
> +struct HashTableValue;

Aren't we supposed to lump these with the struct list below?  See "struct DumpContext" which starts the struct list below.

> Source/JavaScriptCore/tools/JSDollarVM.cpp:647
> +        StaticCustomAccessor* getter = new (NotNull, allocateCell<StaticCustomAccessor>(vm.heap)) StaticCustomAccessor(vm, structure);

/getter/accessor/
Comment 14 Saam Barati 2019-09-30 11:15:19 PDT
Comment on attachment 379590 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=379590&action=review

>> Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp:67
>> +
> 
> Can you explain why this move from above is necessary?  AFAICT, it looks like you just want to flatten dictionaries more aggressively.  Is that correct?  I suppose structure->propertyAccessesAreCacheable() will fail if the structure is for a dictionary.

yes. Precisely, if it's an uncacheable dictionary, we will not flatten it. This is covered by a microbenchmark I added.

>> Source/JavaScriptCore/runtime/ClassInfo.h:221
>>      unsigned staticClassSize;
> 
> You probably missed this field because it only showed up after a rebase.  Can you move it up with the rest as well?

good call.

>> Source/JavaScriptCore/runtime/Structure.h:56
>> +struct HashTableValue;
> 
> Aren't we supposed to lump these with the struct list below?  See "struct DumpContext" which starts the struct list below.

probably. Will fix

>> Source/JavaScriptCore/tools/JSDollarVM.cpp:647
>> +        StaticCustomAccessor* getter = new (NotNull, allocateCell<StaticCustomAccessor>(vm.heap)) StaticCustomAccessor(vm, structure);
> 
> /getter/accessor/

will fix
Comment 15 Yusuke Suzuki 2019-09-30 12:28:39 PDT
Comment on attachment 379590 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=379590&action=review

r=me

> Source/JavaScriptCore/bytecode/AccessCase.cpp:-313
> -    return m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint();

Don't we need to check `if (!m_conditionSet.isValid())`? If we can ensure that this m_conditionSet is always valid, can we add assert instead?

> Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp:-65
> -    return (numberOfConditionsWithKind(PropertyCondition::Presence) == 1) != (numberOfConditionsWithKind(PropertyCondition::Equivalence) == 1);

I think implementing this inline here seems better than iterating three times.
if (size() != 1)
    return false;
const ObjectPropertyCondition& condition = *this.begin();
switch (condition.kind()) {
case PropertyCondition::Presence:
case PropertyCondition::Equivalence:
case PropertyCondition::CustomFunctionEquivalence:
    return true;
...
    return false;
}
return false;

> Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.h:62
> +class AdaptiveValueStructureStubClearingWatchpoint final : public AdaptiveInferredPropertyValueWatchpointBase {

Personally, I think we should rename AdaptiveInferredPropertyValueWatchpointBase to something like `AdaptiveInferredPropertyValueWatchpointCollectionBase` or something because it is not a Watchpoint in fact (It is not inheriting Watchpoint).
Can you add it as a FIXME comment? Or can you rename it?

> Source/JavaScriptCore/bytecode/StructureStubClearingWatchpoint.h:111
> +    Bag<WTF::Variant<StructureTransitionStructureStubClearingWatchpoint, AdaptiveValueStructureStubClearingWatchpoint>> m_watchpoints;

IIRC, this watchpoint class is allocated so many. So, we should save memory as much as possible.
Do we have any ideas? If we do not have an idea for now, please put a FIXME about memory consumption.

> Source/JavaScriptCore/jit/Repatch.cpp:552
> +                                vm, codeBlock, exec, structure, slot.base(), ident.impl(), 0);

I think `static_cast<unsigned>(PropertyAttribute::None)` is better instead of `0`, it is ugly but anyway, it is more readable than 0.

> Source/JavaScriptCore/runtime/ObjectPropertyChangeAdaptiveWatchpoint.h:32
> +template<typename WatchpointSet>

Nice. At some point in the future, we should do this renaming for the other places too :P
Comment 16 Saam Barati 2019-09-30 16:19:00 PDT
Comment on attachment 379590 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=379590&action=review

>> Source/JavaScriptCore/jit/Repatch.cpp:552
>> +                                vm, codeBlock, exec, structure, slot.base(), ident.impl(), 0);
> 
> I think `static_cast<unsigned>(PropertyAttribute::None)` is better instead of `0`, it is ugly but anyway, it is more readable than 0.

will fix
Comment 17 Saam Barati 2019-09-30 17:03:43 PDT
Created attachment 379862 [details]
patch for landing
Comment 18 WebKit Commit Bot 2019-09-30 17:50:53 PDT
Comment on attachment 379862 [details]
patch for landing

Clearing flags on attachment: 379862

Committed r250540: <https://trac.webkit.org/changeset/250540>
Comment 19 WebKit Commit Bot 2019-09-30 17:50:55 PDT
All reviewed patches have been landed.  Closing bug.
Comment 20 Michael Catanzaro 2019-10-04 16:36:32 PDT
Regression: bug #202594