<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[265600] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/265600">265600</a></dd>
<dt>Author</dt> <dd>sbarati@apple.com</dd>
<dt>Date</dt> <dd>2020-08-12 21:50:12 -0700 (Wed, 12 Aug 2020)</dd>
</dl>

<h3>Log Message</h3>
<pre>Inline cache Replace and Setters on PureForwardingProxy
https://bugs.webkit.org/show_bug.cgi?id=215250

Reviewed by Yusuke Suzuki.

JSTests:

* microbenchmarks/property-replace-and-setter-on-js-proxy.js: Added.
(assert):
(foo):

Source/JavaScriptCore:

We didn't used to cache any Puts on PureForwardingProxy. This patch
implements Replace and JS/Custom Setters on PureForwardingProxy. We don't support
Transition puts because in our current implementation different global objects
will never share the same structure.

This patch also aligns how our runtime and the ICs invoke Customs when the
passed in |this| value is a JSProxy. For custom accessors, our runtime passes
in the JSProxy, where our ICs used to pass in the target of the JSProxy, for
the receiver value. For custom values, the IC behavior and the runtime were
already aligned in passing in the property owner, which is the JSProxy's
target. This patch aligns our IC behavior to match our runtime behavior.

This patch also renames some of the registers in the IC code to clear
up what they're used for.

This is a 2.5x speedup on the microbenchmark I've added, and a 15-20% speedup
on JetStream2's 3d-cube-SP.

* bytecode/AccessCase.cpp:
(JSC::AccessCase::generateWithGuard):
(JSC::AccessCase::generateImpl):
* bytecode/GetterSetterAccessCase.cpp:
(JSC::GetterSetterAccessCase::create):
* bytecode/GetterSetterAccessCase.h:
* jit/JITOperations.cpp:
* jit/Repatch.cpp:
(JSC::tryCachePutByID):
* runtime/CommonSlowPaths.h:
(JSC::CommonSlowPaths::originalStructureBeforePut):
(JSC::CommonSlowPaths::putDirectWithReify):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkJSTestsChangeLog">trunk/JSTests/ChangeLog</a></li>
<li><a href="#trunkSourceJavaScriptCoreChangeLog">trunk/Source/JavaScriptCore/ChangeLog</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecodeAccessCasecpp">trunk/Source/JavaScriptCore/bytecode/AccessCase.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecodeGetterSetterAccessCasecpp">trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecodeGetterSetterAccessCaseh">trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecodeProxyableAccessCasecpp">trunk/Source/JavaScriptCore/bytecode/ProxyableAccessCase.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCorejitJITOperationscpp">trunk/Source/JavaScriptCore/jit/JITOperations.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCorejitRepatchcpp">trunk/Source/JavaScriptCore/jit/Repatch.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCorejsccpp">trunk/Source/JavaScriptCore/jsc.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreruntimeCommonSlowPathsh">trunk/Source/JavaScriptCore/runtime/CommonSlowPaths.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreruntimeJSObjectcpp">trunk/Source/JavaScriptCore/runtime/JSObject.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreruntimeOptionsListh">trunk/Source/JavaScriptCore/runtime/OptionsList.h</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkJSTestsmicrobenchmarkspropertyreplaceandsetteronjsproxyjs">trunk/JSTests/microbenchmarks/property-replace-and-setter-on-js-proxy.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkJSTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/JSTests/ChangeLog (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/JSTests/ChangeLog  2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/JSTests/ChangeLog     2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -1,3 +1,14 @@
</span><ins>+2020-08-12  Saam Barati  <sbarati@apple.com>
+
+        Inline cache Replace and Setters on PureForwardingProxy
+        https://bugs.webkit.org/show_bug.cgi?id=215250
+
+        Reviewed by Yusuke Suzuki.
+
+        * microbenchmarks/property-replace-and-setter-on-js-proxy.js: Added.
+        (assert):
+        (foo):
+
</ins><span class="cx"> 2020-08-07  Michael Saboff  <msaboff@apple.com>
</span><span class="cx"> 
</span><span class="cx">         RegExp sticky not matching alternates correctly, ignoring lastIndex requirement
</span></span></pre></div>
<a id="trunkJSTestsmicrobenchmarkspropertyreplaceandsetteronjsproxyjs"></a>
<div class="addfile"><h4>Added: trunk/JSTests/microbenchmarks/property-replace-and-setter-on-js-proxy.js (0 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/JSTests/microbenchmarks/property-replace-and-setter-on-js-proxy.js                         (rev 0)
+++ trunk/JSTests/microbenchmarks/property-replace-and-setter-on-js-proxy.js    2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -0,0 +1,32 @@
</span><ins>+//@ requireOptions("--exposeCustomSettersOnGlobalObjectForTesting=true")
+
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+let global = this;
+Object.defineProperty(global, "Y", {
+    set: function(v) {
+        assert(this === global);
+        assert(v === i + 1);
+        this._Y = v;
+    }
+});
+
+function foo(x, y, z, a) {
+    this.X = x;
+    this.Y = y;
+    this.testCustomAccessorSetter = z;
+    this.testCustomValueSetter = a;
+}
+noInline(foo);
+
+let i;
+for (i = 0; i < 1000000; ++i) {
+    foo(i, i + 1, i + 2, i + 3);
+    assert(global.X === i);
+    assert(global._Y === i + 1);
+    assert(global._testCustomAccessorSetter === i + 2);
+    assert(global._testCustomValueSetter === i + 3);
+}
</ins></span></pre></div>
<a id="trunkSourceJavaScriptCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/ChangeLog (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/ChangeLog    2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/ChangeLog       2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -1,3 +1,41 @@
</span><ins>+2020-08-12  Saam Barati  <sbarati@apple.com>
+
+        Inline cache Replace and Setters on PureForwardingProxy
+        https://bugs.webkit.org/show_bug.cgi?id=215250
+
+        Reviewed by Yusuke Suzuki.
+
+        We didn't used to cache any Puts on PureForwardingProxy. This patch
+        implements Replace and JS/Custom Setters on PureForwardingProxy. We don't support
+        Transition puts because in our current implementation different global objects
+        will never share the same structure.
+        
+        This patch also aligns how our runtime and the ICs invoke Customs when the
+        passed in |this| value is a JSProxy. For custom accessors, our runtime passes
+        in the JSProxy, where our ICs used to pass in the target of the JSProxy, for
+        the receiver value. For custom values, the IC behavior and the runtime were
+        already aligned in passing in the property owner, which is the JSProxy's
+        target. This patch aligns our IC behavior to match our runtime behavior.
+        
+        This patch also renames some of the registers in the IC code to clear
+        up what they're used for.
+        
+        This is a 2.5x speedup on the microbenchmark I've added, and a 15-20% speedup
+        on JetStream2's 3d-cube-SP.
+
+        * bytecode/AccessCase.cpp:
+        (JSC::AccessCase::generateWithGuard):
+        (JSC::AccessCase::generateImpl):
+        * bytecode/GetterSetterAccessCase.cpp:
+        (JSC::GetterSetterAccessCase::create):
+        * bytecode/GetterSetterAccessCase.h:
+        * jit/JITOperations.cpp:
+        * jit/Repatch.cpp:
+        (JSC::tryCachePutByID):
+        * runtime/CommonSlowPaths.h:
+        (JSC::CommonSlowPaths::originalStructureBeforePut):
+        (JSC::CommonSlowPaths::putDirectWithReify):
+
</ins><span class="cx"> 2020-08-11  Mark Lam  <mark.lam@apple.com>
</span><span class="cx"> 
</span><span class="cx">         ScriptExecutable::newCodeBlockFor() neglected to set the exception pointer result in one case.
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecodeAccessCasecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecode/AccessCase.cpp (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecode/AccessCase.cpp      2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/bytecode/AccessCase.cpp 2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -786,6 +786,7 @@
</span><span class="cx"> 
</span><span class="cx">     auto emitDefaultGuard = [&] () {
</span><span class="cx">         if (m_polyProtoAccessChain) {
</span><ins>+            ASSERT(!viaProxy());
</ins><span class="cx">             GPRReg baseForAccessGPR = state.scratchGPR;
</span><span class="cx">             jit.move(state.baseGPR, baseForAccessGPR);
</span><span class="cx">             m_polyProtoAccessChain->forEach(vm, structure(), [&] (Structure* structure, bool atEnd) {
</span><span class="lines">@@ -904,11 +905,13 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     case ModuleNamespaceLoad: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         this->as<ModuleNamespaceAccessCase>().emit(state, fallThrough);
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     case IndexedScopedArgumentsLoad: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         // This code is written such that the result could alias with the base or the property.
</span><span class="cx">         GPRReg propertyGPR = state.u.propertyGPR;
</span><span class="cx"> 
</span><span class="lines">@@ -974,6 +977,7 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     case IndexedDirectArgumentsLoad: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         // This code is written such that the result could alias with the base or the property.
</span><span class="cx">         GPRReg propertyGPR = state.u.propertyGPR;
</span><span class="cx">         jit.load8(CCallHelpers::Address(baseGPR, JSCell::typeInfoTypeOffset()), scratchGPR);
</span><span class="lines">@@ -997,6 +1001,7 @@
</span><span class="cx">     case IndexedTypedArrayUint32Load:
</span><span class="cx">     case IndexedTypedArrayFloat32Load:
</span><span class="cx">     case IndexedTypedArrayFloat64Load: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         // This code is written such that the result could alias with the base or the property.
</span><span class="cx"> 
</span><span class="cx">         TypedArrayType type = toTypedArrayType(m_type);
</span><span class="lines">@@ -1087,6 +1092,7 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     case IndexedStringLoad: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         // This code is written such that the result could alias with the base or the property.
</span><span class="cx">         GPRReg propertyGPR = state.u.propertyGPR;
</span><span class="cx"> 
</span><span class="lines">@@ -1142,6 +1148,7 @@
</span><span class="cx">     case IndexedDoubleLoad:
</span><span class="cx">     case IndexedContiguousLoad:
</span><span class="cx">     case IndexedArrayStorageLoad: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         // This code is written such that the result could alias with the base or the property.
</span><span class="cx">         GPRReg propertyGPR = state.u.propertyGPR;
</span><span class="cx"> 
</span><span class="lines">@@ -1258,6 +1265,7 @@
</span><span class="cx">         break;
</span><span class="cx">         
</span><span class="cx">     case InstanceOfGeneric: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         GPRReg prototypeGPR = state.u.prototypeGPR;
</span><span class="cx">         // Legend: value = `base instanceof prototypeGPR`.
</span><span class="cx">         
</span><span class="lines">@@ -1426,40 +1434,47 @@
</span><span class="cx">             currStructure->startWatchingPropertyForReplacements(vm, offset());
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        GPRReg baseForGetGPR;
-        if (viaProxy()) {
-            ASSERT(m_type != CustomValueSetter || m_type != CustomAccessorSetter); // Because setters need to not trash valueRegsPayloadGPR.
-            if (m_type == Getter || m_type == Setter)
-                baseForGetGPR = scratchGPR;
-            else
-                baseForGetGPR = valueRegsPayloadGPR;
</del><ins>+        bool doesPropertyStorageLoads = m_type == Load 
+            || m_type == GetGetter
+            || m_type == Getter
+            || m_type == Setter;
</ins><span class="cx"> 
</span><del>-            ASSERT((m_type != Getter && m_type != Setter) || baseForGetGPR != baseGPR);
-            ASSERT(m_type != Setter || baseForGetGPR != valueRegsPayloadGPR);
</del><ins>+        bool takesPropertyOwnerAsCFunctionArgument = m_type == CustomValueGetter || m_type == CustomValueSetter;
</ins><span class="cx"> 
</span><del>-            jit.loadPtr(
-                CCallHelpers::Address(baseGPR, JSProxy::targetOffset()),
-                baseForGetGPR);
-        } else
-            baseForGetGPR = baseGPR;
</del><ins>+        GPRReg receiverGPR = baseGPR;
+        GPRReg propertyOwnerGPR;
</ins><span class="cx"> 
</span><del>-        GPRReg baseForAccessGPR;
</del><span class="cx">         if (m_polyProtoAccessChain) {
</span><span class="cx">             // This isn't pretty, but we know we got here via generateWithGuard,
</span><span class="cx">             // and it left the baseForAccess inside scratchGPR. We could re-derive the base,
</span><span class="cx">             // but it'd require emitting the same code to load the base twice.
</span><del>-            baseForAccessGPR = scratchGPR;
-        } else {
-            if (hasAlternateBase()) {
-                jit.move(
-                    CCallHelpers::TrustedImmPtr(alternateBase()), scratchGPR);
-                baseForAccessGPR = scratchGPR;
-            } else
-                baseForAccessGPR = baseForGetGPR;
-        }
</del><ins>+            propertyOwnerGPR = scratchGPR;
+        } else if (hasAlternateBase()) {
+            jit.move(
+                CCallHelpers::TrustedImmPtr(alternateBase()), scratchGPR);
+            propertyOwnerGPR = scratchGPR;
+        } else if (viaProxy() && doesPropertyStorageLoads) {
+            // We only need this when loading an inline or out of line property. For customs accessors,
+            // we can invoke with a receiver value that is a JSProxy. For custom values, we unbox to the
+            // JSProxy's target. For getters/setters, we'll also invoke them with the JSProxy as |this|,
+            // but we need to load the actual GetterSetter cell from the JSProxy's target.
</ins><span class="cx"> 
</span><ins>+            if (m_type == Getter || m_type == Setter)
+                propertyOwnerGPR = scratchGPR;
+            else
+                propertyOwnerGPR = valueRegsPayloadGPR;
+
+            jit.loadPtr(
+                CCallHelpers::Address(baseGPR, JSProxy::targetOffset()), propertyOwnerGPR);
+        } else if (viaProxy() && takesPropertyOwnerAsCFunctionArgument) {
+            propertyOwnerGPR = scratchGPR;
+            jit.loadPtr(
+                CCallHelpers::Address(baseGPR, JSProxy::targetOffset()), propertyOwnerGPR);
+        } else
+            propertyOwnerGPR = receiverGPR;
+
</ins><span class="cx">         GPRReg loadedValueGPR = InvalidGPRReg;
</span><del>-        if (m_type != CustomValueGetter && m_type != CustomAccessorGetter && m_type != CustomValueSetter && m_type != CustomAccessorSetter) {
</del><ins>+        if (doesPropertyStorageLoads) {
</ins><span class="cx">             if (m_type == Load || m_type == GetGetter)
</span><span class="cx">                 loadedValueGPR = valueRegsPayloadGPR;
</span><span class="cx">             else
</span><span class="lines">@@ -1470,10 +1485,10 @@
</span><span class="cx"> 
</span><span class="cx">             GPRReg storageGPR;
</span><span class="cx">             if (isInlineOffset(m_offset))
</span><del>-                storageGPR = baseForAccessGPR;
</del><ins>+                storageGPR = propertyOwnerGPR;
</ins><span class="cx">             else {
</span><span class="cx">                 jit.loadPtr(
</span><del>-                    CCallHelpers::Address(baseForAccessGPR, JSObject::butterflyOffset()),
</del><ins>+                    CCallHelpers::Address(propertyOwnerGPR, JSObject::butterflyOffset()),
</ins><span class="cx">                     loadedValueGPR);
</span><span class="cx">                 storageGPR = loadedValueGPR;
</span><span class="cx">             }
</span><span class="lines">@@ -1509,7 +1524,7 @@
</span><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             if (Options::useDOMJIT() && access.domAttribute()->domJIT) {
</span><del>-                access.emitDOMJITGetter(state, access.domAttribute()->domJIT, baseForGetGPR);
</del><ins>+                access.emitDOMJITGetter(state, access.domAttribute()->domJIT, receiverGPR);
</ins><span class="cx">                 return;
</span><span class="cx">             }
</span><span class="cx">         }
</span><span class="lines">@@ -1684,6 +1699,7 @@
</span><span class="cx">             });
</span><span class="cx">         } else {
</span><span class="cx">             ASSERT(m_type == CustomValueGetter || m_type == CustomAccessorGetter || m_type == CustomValueSetter || m_type == CustomAccessorSetter);
</span><ins>+            ASSERT(!doesPropertyStorageLoads); // Or we need an extra register. We rely on propertyOwnerGPR being correct here.
</ins><span class="cx"> 
</span><span class="cx">             // Need to make room for the C call so any of our stack spillage isn't overwritten. It's
</span><span class="cx">             // hard to track if someone did spillage or not, so we just assume that we always need
</span><span class="lines">@@ -1691,14 +1707,14 @@
</span><span class="cx">             jit.makeSpaceOnStackForCCall();
</span><span class="cx"> 
</span><span class="cx">             // Check if it is a super access
</span><del>-            GPRReg baseForCustomGetGPR = baseGPR != thisGPR ? thisGPR : baseForGetGPR;
</del><ins>+            GPRReg receiverForCustomGetGPR = baseGPR != thisGPR ? thisGPR : receiverGPR;
</ins><span class="cx"> 
</span><span class="cx">             // getter: EncodedJSValue (*GetValueFunc)(JSGlobalObject*, EncodedJSValue thisValue, PropertyName);
</span><span class="cx">             // setter: void (*PutValueFunc)(JSGlobalObject*, EncodedJSValue thisObject, EncodedJSValue value);
</span><del>-            // Custom values are passed the slotBase (the property holder), custom accessors are passed the thisVaule (reciever).
</del><ins>+            // Custom values are passed the slotBase (the property holder), custom accessors are passed the thisVaule (receiver).
</ins><span class="cx">             // FIXME: Remove this differences in custom values and custom accessors.
</span><span class="cx">             // https://bugs.webkit.org/show_bug.cgi?id=158014
</span><del>-            GPRReg baseForCustom = m_type == CustomValueGetter || m_type == CustomValueSetter ? baseForAccessGPR : baseForCustomGetGPR; 
</del><ins>+            GPRReg baseForCustom = takesPropertyOwnerAsCFunctionArgument ? propertyOwnerGPR : receiverForCustomGetGPR; 
</ins><span class="cx">             // We do not need to keep globalObject alive since the owner CodeBlock (even if JSGlobalObject* is one of CodeBlock that is inlined and held by DFG CodeBlock)
</span><span class="cx">             // must keep it alive.
</span><span class="cx">             if (m_type == CustomValueGetter || m_type == CustomAccessorGetter) {
</span><span class="lines">@@ -1739,15 +1755,21 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     case Replace: {
</span><ins>+        GPRReg base = baseGPR;
+        if (viaProxy()) {
+            // This aint pretty, but the path that structure checks loads the real base into scratchGPR.
+            base = scratchGPR;
+        }
+
</ins><span class="cx">         if (isInlineOffset(m_offset)) {
</span><span class="cx">             jit.storeValue(
</span><span class="cx">                 valueRegs,
</span><span class="cx">                 CCallHelpers::Address(
</span><del>-                    baseGPR,
</del><ins>+                    base,
</ins><span class="cx">                     JSObject::offsetOfInlineStorage() +
</span><span class="cx">                     offsetInInlineStorage(m_offset) * sizeof(JSValue)));
</span><span class="cx">         } else {
</span><del>-            jit.loadPtr(CCallHelpers::Address(baseGPR, JSObject::butterflyOffset()), scratchGPR);
</del><ins>+            jit.loadPtr(CCallHelpers::Address(base, JSObject::butterflyOffset()), scratchGPR);
</ins><span class="cx">             jit.storeValue(
</span><span class="cx">                 valueRegs,
</span><span class="cx">                 CCallHelpers::Address(
</span><span class="lines">@@ -1758,6 +1780,7 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     case Transition: {
</span><ins>+        ASSERT(!viaProxy());
</ins><span class="cx">         // AccessCase::transition() should have returned null if this wasn't true.
</span><span class="cx">         RELEASE_ASSERT(GPRInfo::numberOfRegisters >= 6 || !structure()->outOfLineCapacity() || structure()->outOfLineCapacity() == newStructure()->outOfLineCapacity());
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecodeGetterSetterAccessCasecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp  2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp     2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -68,11 +68,11 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> std::unique_ptr<AccessCase> GetterSetterAccessCase::create(VM& vm, JSCell* owner, AccessType type, Structure* structure, CacheableIdentifier identifier, PropertyOffset offset,
</span><del>-    const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain, FunctionPtr<OperationPtrTag> customSetter,
-    JSObject* customSlotBase)
</del><ins>+    const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain, bool viaProxy, 
+    FunctionPtr<OperationPtrTag> customSetter, JSObject* customSlotBase)
</ins><span class="cx"> {
</span><span class="cx">     ASSERT(type == Setter || type == CustomValueSetter || type == CustomAccessorSetter);
</span><del>-    std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, identifier, offset, structure, conditionSet, false, nullptr, customSlotBase, WTFMove(prototypeAccessChain)));
</del><ins>+    std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, identifier, offset, structure, conditionSet, viaProxy, nullptr, customSlotBase, WTFMove(prototypeAccessChain)));
</ins><span class="cx">     result->m_customAccessor = customSetter ? FunctionPtr<OperationPtrTag>(customSetter) : nullptr;
</span><span class="cx">     return result;
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecodeGetterSetterAccessCaseh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h    2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h       2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -59,7 +59,7 @@
</span><span class="cx">         JSObject* customSlotBase, Optional<DOMAttributeAnnotation>, std::unique_ptr<PolyProtoAccessChain>);
</span><span class="cx"> 
</span><span class="cx">     static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, AccessType, Structure*, CacheableIdentifier, PropertyOffset,
</span><del>-        const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>,
</del><ins>+        const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>, bool viaProxy = false,
</ins><span class="cx">         FunctionPtr<OperationPtrTag> customSetter = nullptr, JSObject* customSlotBase = nullptr);
</span><span class="cx"> 
</span><span class="cx">     void dumpImpl(PrintStream&, CommaPrinter&) const final;
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecodeProxyableAccessCasecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecode/ProxyableAccessCase.cpp (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecode/ProxyableAccessCase.cpp     2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/bytecode/ProxyableAccessCase.cpp        2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -40,7 +40,7 @@
</span><span class="cx"> 
</span><span class="cx"> std::unique_ptr<AccessCase> ProxyableAccessCase::create(VM& vm, JSCell* owner, AccessType type, CacheableIdentifier identifier, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
</span><span class="cx"> {
</span><del>-    ASSERT(type == Load || type == Miss || type == GetGetter);
</del><ins>+    ASSERT(type == Load || type == Miss || type == GetGetter || type == Replace);
</ins><span class="cx">     return std::unique_ptr<AccessCase>(new ProxyableAccessCase(vm, owner, type, identifier, offset, structure, conditionSet, viaProxy, additionalSet, WTFMove(prototypeAccessChain)));
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorejitJITOperationscpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/jit/JITOperations.cpp (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/jit/JITOperations.cpp        2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/jit/JITOperations.cpp   2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -589,7 +589,7 @@
</span><span class="cx">     CodeBlock* codeBlock = callFrame->codeBlock();
</span><span class="cx">     PutPropertySlot slot(baseValue, true, codeBlock->putByIdContext());
</span><span class="cx"> 
</span><del>-    Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(vm) : nullptr;
</del><ins>+    Structure* structure = CommonSlowPaths::originalStructureBeforePut(vm, baseValue);
</ins><span class="cx">     baseValue.putInline(globalObject, ident, value, slot);
</span><span class="cx"> 
</span><span class="cx">     LOG_IC((ICEvent::OperationPutByIdStrictOptimize, baseValue.classInfoOrNull(vm), ident, slot.base() == baseValue));
</span><span class="lines">@@ -621,7 +621,7 @@
</span><span class="cx">     CodeBlock* codeBlock = callFrame->codeBlock();
</span><span class="cx">     PutPropertySlot slot(baseValue, false, codeBlock->putByIdContext());
</span><span class="cx"> 
</span><del>-    Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(vm) : nullptr;
</del><ins>+    Structure* structure = CommonSlowPaths::originalStructureBeforePut(vm, baseValue);
</ins><span class="cx">     baseValue.putInline(globalObject, ident, value, slot);
</span><span class="cx"> 
</span><span class="cx">     LOG_IC((ICEvent::OperationPutByIdNonStrictOptimize, baseValue.classInfoOrNull(vm), ident, slot.base() == baseValue));
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorejitRepatchcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/jit/Repatch.cpp (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/jit/Repatch.cpp      2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/jit/Repatch.cpp 2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -591,9 +591,26 @@
</span><span class="cx">         if (!oldStructure->propertyAccessesAreCacheable())
</span><span class="cx">             return GiveUpOnCache;
</span><span class="cx"> 
</span><del>-        std::unique_ptr<AccessCase> newCase;
</del><span class="cx">         JSCell* baseCell = baseValue.asCell();
</span><span class="cx"> 
</span><ins>+        bool isProxy = false;
+        if (baseCell->type() == PureForwardingProxyType) {
+            baseCell = jsCast<JSProxy*>(baseCell)->target();
+            baseValue = baseCell;
+            isProxy = true;
+
+            // We currently only cache Replace and JS/Custom Setters on JSProxy. We don't
+            // cache transitions because global objects will never share the same structure
+            // in our current implementation.
+            bool isCacheableProxy = (slot.isCacheablePut() && slot.type() == PutPropertySlot::ExistingProperty)
+                || slot.isCacheableSetter()
+                || slot.isCacheableCustom();
+            if (!isCacheableProxy)
+                return GiveUpOnCache;
+        }
+
+        std::unique_ptr<AccessCase> newCase;
+
</ins><span class="cx">         if (slot.base() == baseValue && slot.isCacheablePut()) {
</span><span class="cx">             if (slot.type() == PutPropertySlot::ExistingProperty) {
</span><span class="cx">                 // This assert helps catch bugs if we accidentally forget to disable caching
</span><span class="lines">@@ -608,7 +625,8 @@
</span><span class="cx">             
</span><span class="cx">                 if (stubInfo.cacheType() == CacheType::Unset
</span><span class="cx">                     && InlineAccess::canGenerateSelfPropertyReplace(stubInfo, slot.cachedOffset())
</span><del>-                    && !oldStructure->needImpurePropertyWatchpoint()) {
</del><ins>+                    && !oldStructure->needImpurePropertyWatchpoint()
+                    && !isProxy) {
</ins><span class="cx">                     
</span><span class="cx">                     bool generatedCodeInline = InlineAccess::generateSelfPropertyReplace(stubInfo, oldStructure, slot.cachedOffset());
</span><span class="cx">                     if (generatedCodeInline) {
</span><span class="lines">@@ -619,8 +637,9 @@
</span><span class="cx">                     }
</span><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                newCase = AccessCase::create(vm, codeBlock, AccessCase::Replace, propertyName, slot.cachedOffset(), oldStructure);
</del><ins>+                newCase = ProxyableAccessCase::create(vm, codeBlock, AccessCase::Replace, propertyName, slot.cachedOffset(), oldStructure, ObjectPropertyConditionSet(), isProxy);
</ins><span class="cx">             } else {
</span><ins>+                ASSERT(!isProxy);
</ins><span class="cx">                 ASSERT(slot.type() == PutPropertySlot::NewProperty);
</span><span class="cx"> 
</span><span class="cx">                 if (!oldStructure->isObject())
</span><span class="lines">@@ -706,8 +725,9 @@
</span><span class="cx"> 
</span><span class="cx">                 newCase = GetterSetterAccessCase::create(
</span><span class="cx">                     vm, codeBlock, slot.isCustomAccessor() ? AccessCase::CustomAccessorSetter : AccessCase::CustomValueSetter, oldStructure, propertyName,
</span><del>-                    invalidOffset, conditionSet, WTFMove(prototypeAccessChain), slot.customSetter(), slot.base() != baseValue ? slot.base() : nullptr);
</del><ins>+                    invalidOffset, conditionSet, WTFMove(prototypeAccessChain), isProxy, slot.customSetter(), slot.base() != baseValue ? slot.base() : nullptr);
</ins><span class="cx">             } else {
</span><ins>+                ASSERT(slot.isCacheableSetter());
</ins><span class="cx">                 ObjectPropertyConditionSet conditionSet;
</span><span class="cx">                 std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
</span><span class="cx">                 PropertyOffset offset = slot.cachedOffset();
</span><span class="lines">@@ -742,7 +762,7 @@
</span><span class="cx">                 }
</span><span class="cx"> 
</span><span class="cx">                 newCase = GetterSetterAccessCase::create(
</span><del>-                    vm, codeBlock, AccessCase::Setter, oldStructure, propertyName, offset, conditionSet, WTFMove(prototypeAccessChain));
</del><ins>+                    vm, codeBlock, AccessCase::Setter, oldStructure, propertyName, offset, conditionSet, WTFMove(prototypeAccessChain), isProxy);
</ins><span class="cx">             }
</span><span class="cx">         }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorejsccpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/jsc.cpp (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/jsc.cpp      2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/jsc.cpp 2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -638,7 +638,52 @@
</span><span class="cx">         addFunction(vm, "setUnhandledRejectionCallback", functionSetUnhandledRejectionCallback, 1);
</span><span class="cx"> 
</span><span class="cx">         addFunction(vm, "asDoubleNumber", functionAsDoubleNumber, 1);
</span><ins>+
+        if (Options::exposeCustomSettersOnGlobalObjectForTesting()) {
+            {
+                CustomGetterSetter* custom = CustomGetterSetter::create(vm, nullptr, testCustomAccessorSetter);
+                Identifier identifier = Identifier::fromString(vm, "testCustomAccessorSetter");
+                this->putDirectCustomAccessor(vm, identifier, custom, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::CustomAccessor);
+            }
+
+            {
+                CustomGetterSetter* custom = CustomGetterSetter::create(vm, nullptr, testCustomValueSetter);
+                Identifier identifier = Identifier::fromString(vm, "testCustomValueSetter");
+                this->putDirectCustomAccessor(vm, identifier, custom, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::CustomValue);
+            }
+        }
</ins><span class="cx">     }
</span><ins>+
+    static bool testCustomSetterImpl(JSGlobalObject* lexicalGlobalObject, GlobalObject* thisObject, EncodedJSValue encodedValue, const char* propertyName)
+    {
+        VM& vm = lexicalGlobalObject->vm();
+
+        Identifier identifier = Identifier::fromString(vm, propertyName);
+        thisObject->putDirect(vm, identifier, JSValue::decode(encodedValue), DontEnum);
+
+        return true;
+    }
+
+    static bool testCustomAccessorSetter(JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+    {
+        VM& vm = lexicalGlobalObject->vm();
+        RELEASE_ASSERT(JSValue::decode(thisValue).isCell());
+        JSCell* thisCell = JSValue::decode(thisValue).asCell();
+        RELEASE_ASSERT(thisCell->type() == PureForwardingProxyType);
+        GlobalObject* thisObject = jsDynamicCast<GlobalObject*>(vm, jsCast<JSProxy*>(thisCell)->target());
+        RELEASE_ASSERT(thisObject);
+        return testCustomSetterImpl(lexicalGlobalObject, thisObject, encodedValue, "_testCustomAccessorSetter");
+    }
+
+    static bool testCustomValueSetter(JSGlobalObject* lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+    {
+        VM& vm = lexicalGlobalObject->vm();
+        RELEASE_ASSERT(JSValue::decode(thisValue).isCell());
+        JSCell* thisCell = JSValue::decode(thisValue).asCell();
+        GlobalObject* thisObject = jsDynamicCast<GlobalObject*>(vm, thisCell);
+        RELEASE_ASSERT(thisObject);
+        return testCustomSetterImpl(lexicalGlobalObject, thisObject, encodedValue, "_testCustomValueSetter");
+    }
</ins><span class="cx">     
</span><span class="cx">     void addFunction(VM& vm, JSObject* object, const char* name, NativeFunction function, unsigned arguments)
</span><span class="cx">     {
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreruntimeCommonSlowPathsh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/runtime/CommonSlowPaths.h (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/runtime/CommonSlowPaths.h    2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/runtime/CommonSlowPaths.h       2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -137,6 +137,16 @@
</span><span class="cx">     return false;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+ALWAYS_INLINE Structure* originalStructureBeforePut(VM& vm, JSValue value)
+{
+    if (!value.isCell())
+        return nullptr;
+    if (value.asCell()->type() == PureForwardingProxyType)
+        return jsCast<JSProxy*>(value)->target()->structure(vm);
+    return value.asCell()->structure(vm);
+}
+
+
</ins><span class="cx"> static ALWAYS_INLINE void putDirectWithReify(VM& vm, JSGlobalObject* globalObject, JSObject* baseObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot, Structure** result = nullptr)
</span><span class="cx"> {
</span><span class="cx">     auto scope = DECLARE_THROW_SCOPE(vm);
</span><span class="lines">@@ -145,7 +155,7 @@
</span><span class="cx">         RETURN_IF_EXCEPTION(scope, void());
</span><span class="cx">     }
</span><span class="cx">     if (result)
</span><del>-        *result = baseObject->structure(vm);
</del><ins>+        *result = originalStructureBeforePut(vm, baseObject);
</ins><span class="cx">     scope.release();
</span><span class="cx">     baseObject->putDirect(vm, propertyName, value, slot);
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreruntimeJSObjectcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/runtime/JSObject.cpp (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/runtime/JSObject.cpp 2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/runtime/JSObject.cpp    2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -841,7 +841,7 @@
</span><span class="cx">             JSValue gs = obj->getDirect(offset);
</span><span class="cx">             if (gs.isGetterSetter()) {
</span><span class="cx">                 // We need to make sure that we decide to cache this property before we potentially execute aribitrary JS.
</span><del>-                if (!this->structure(vm)->isDictionary())
</del><ins>+                if (!this->structure(vm)->isUncacheableDictionary())
</ins><span class="cx">                     slot.setCacheableSetter(obj, offset);
</span><span class="cx"> 
</span><span class="cx">                 bool result = callSetter(globalObject, slot.thisValue(), gs, value, slot.isStrictMode() ? ECMAMode::strict() : ECMAMode::sloppy());
</span><span class="lines">@@ -849,6 +849,9 @@
</span><span class="cx">                 return result;
</span><span class="cx">             }
</span><span class="cx">             if (gs.isCustomGetterSetter()) {
</span><ins>+                // FIXME: We should only be caching these if we're not an uncacheable dictionary:
+                // https://bugs.webkit.org/show_bug.cgi?id=215347
+
</ins><span class="cx">                 // We need to make sure that we decide to cache this property before we potentially execute aribitrary JS.
</span><span class="cx">                 if (attributes & PropertyAttribute::CustomAccessor)
</span><span class="cx">                     slot.setCustomAccessor(obj, jsCast<CustomGetterSetter*>(gs.asCell())->setter());
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreruntimeOptionsListh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/runtime/OptionsList.h (265599 => 265600)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/runtime/OptionsList.h        2020-08-13 04:39:01 UTC (rev 265599)
+++ trunk/Source/JavaScriptCore/runtime/OptionsList.h   2020-08-13 04:50:12 UTC (rev 265600)
</span><span class="lines">@@ -518,6 +518,7 @@
</span><span class="cx">     v(Bool, useLICMFuzzing, false, Normal, nullptr) \
</span><span class="cx">     v(Unsigned, seedForLICMFuzzer, 424242, Normal, nullptr) \
</span><span class="cx">     v(Double, allowHoistingLICMProbability, 0.5, Normal, nullptr) \
</span><ins>+    v(Bool, exposeCustomSettersOnGlobalObjectForTesting, false, Normal, nullptr) \
</ins><span class="cx"> 
</span><span class="cx"> enum OptionEquivalence {
</span><span class="cx">     SameOption,
</span></span></pre>
</div>
</div>

</body>
</html>