Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JSC] Skip ProxyObject's ICs trap result validation in the common case #12919

Merged
merged 1 commit into from
Apr 27, 2023

Conversation

shvaikalesh
Copy link
Member

@shvaikalesh shvaikalesh commented Apr 19, 2023

18d9fef

[JSC] Skip ProxyObject's ICs trap result validation in the common case
https://bugs.webkit.org/show_bug.cgi?id=255661
<rdar://problem/108269769>

Reviewed by Yusuke Suzuki.

Invariants of the essential internal methods [1] are enforced for all objects, both ordinary and exotic,
to avoid them having unpredictable behavior that negatively impacts developer experience and creates
security issues.

Enforcing them for Proxy objects is especially cumbersome, given traps are userland code, and quite
expensive performance-wise: invariants invalidation requires checking property attributes,
which currently requires JS => C++ call.

After carefully analyzing all the invariants of the all internal methods, it was concluded that if
[[ProxyTarget]] is an ordinary extensible object without non-configurable properties, validation of
the invariants can be safely (non-observably) skipped.

trap name                   IC    attributes checked
========================    ==    ==================================
getOwnPropertyDescriptor    no    non-configurable || non-extensible
defineProperty              no    non-configurable || non-extensible
has                        yes    non-configurable || non-extensible
get                        yes    non-configurable && (non-writable || accessor)
set                        yes    non-configurable && (non-writable || accessor)
deleteProperty           maybe    non-configurable || non-extensible
ownKeys                     no    non-configurable || non-extensible

This change:

  1. Introduces OverridesIsExtensible out-of-line type info flag to tighten Structure's didPreventExtensions()
     flag, making it more pessimistic: it being `true` doesn't necessarily mean the structure is non-extensible,
     but the reverse of it being `false` can be safely relied on.

     It's not a completely new approach: #11843 does the same thing.

     This change was proved not to introduce bugs since Structure's didPreventExtensions() flag is currently
     checked only for objects without isExtensible() override.

  2. Introduces hasNonConfigurableProperties and hasNonConfigurableReadOnlyOrGetterSetterProperties Structure
     flags that are also pessimistic.

     When inferring their initial values, we do extra care not to be overly pessimistic for objects except
     JSFinalObject by checking overridesGetOwnPropertySlot() so that userland code, which heavily uses
     Map / Set as [[ProxyTarget]], more performant, while making an exception for JSArray's non-configurable
     non-structure "length" property.

     While there is existing hasReadOnlyOrGetterSetterPropertiesExcludingProto flag, we can't combine it with
     hasNonConfigurableProperties because MobX installs a non-writable administration property even on arrays,
     which have non-configurable "length" already, so combining them would disable optimization for JSArray.

  3. Introduces HasStructureWithFlags bytecode op to be utilized in ProxyObject IC helpers to skip trap result
     validation for most of ProxyObject's targets. In the follow-ups, this approach will be expanded to C++ code.

Currently, due to lack of proper profiling of GetInternalField results, constant folding for HasStructureWithFlags
doesn't work in microbenchmarks.

                                   ToT                      patch

proxy-has-miss              159.9825+-2.4190     ^     43.1435+-0.5994        ^ definitely 3.7082x faster
proxy-set-miss-handler      158.7617+-2.3615     ^    139.2158+-1.9197        ^ definitely 1.1404x faster
proxy-set                   125.5967+-1.3678     ^     14.4533+-0.1739        ^ definitely 8.6898x faster
proxy-get                   115.8061+-2.2413     ^     14.2136+-0.1138        ^ definitely 8.1475x faster

<geometric>                 138.5985+-0.9526     ^     33.3237+-0.2255        ^ definitely 4.1592x faster

[1]: https://tc39.es/ecma262/#sec-invariants-of-the-essential-internal-methods

* Source/JavaScriptCore/builtins/BuiltinNames.h:
* Source/JavaScriptCore/builtins/ProxyHelpers.js:
(linkTimeConstant.performProxyObjectHas):
(linkTimeConstant.performProxyObjectGet):
(linkTimeConstant.performProxyObjectSetSloppy):
(linkTimeConstant.performProxyObjectSetStrict):
* Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h:
* Source/JavaScriptCore/bytecode/BytecodeList.rb:
* Source/JavaScriptCore/bytecode/BytecodeUseDef.cpp:
(JSC::computeUsesForBytecodeIndexImpl):
(JSC::computeDefsForBytecodeIndexImpl):
* Source/JavaScriptCore/bytecode/LinkTimeConstant.h:
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitHasStructureWithFlags):
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:
* Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp:
(JSC::BytecodeIntrinsicNode::emit_intrinsic_mustValidateResultOfProxyGetAndSetTraps):
(JSC::BytecodeIntrinsicNode::emit_intrinsic_mustValidateResultOfProxyTrapsExceptGetAndSet):
* Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* Source/JavaScriptCore/dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* Source/JavaScriptCore/dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* Source/JavaScriptCore/dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* Source/JavaScriptCore/dfg/DFGHeapLocation.cpp:
(WTF::printInternal):
* Source/JavaScriptCore/dfg/DFGHeapLocation.h:
* Source/JavaScriptCore/dfg/DFGNode.h:
(JSC::DFG::Node::structureFlags):
* Source/JavaScriptCore/dfg/DFGNodeType.h:
* Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp:
* Source/JavaScriptCore/dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h:
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* Source/JavaScriptCore/ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileCompareStrictEq):
* Source/JavaScriptCore/jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
* Source/JavaScriptCore/jit/JIT.h:
* Source/JavaScriptCore/jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_has_structure_with_flags):
* Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm:
* Source/JavaScriptCore/llint/LowLevelInterpreter64.asm:
* Source/JavaScriptCore/runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
* Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):
(JSC::globalFuncHandleProxySetTrapResult): Deleted.
* Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.h:
* Source/JavaScriptCore/runtime/JSTypeInfo.h:
(JSC::TypeInfo::overridesIsExtensible const):
* Source/JavaScriptCore/runtime/PropertySlot.h:
* Source/JavaScriptCore/runtime/ProxyObject.cpp:
(JSC::ProxyObject::performPut):
(JSC::ProxyObject::validatePositiveSetTrapResult):
(JSC::ProxyObject::validateSetTrapResult): Deleted.
* Source/JavaScriptCore/runtime/ProxyObject.h:
* Source/JavaScriptCore/runtime/Structure.cpp:
(JSC::Structure::validateFlags):
(JSC::Structure::Structure):
(JSC::Structure::nonPropertyTransitionSlow):
* Source/JavaScriptCore/runtime/Structure.h:
(JSC::Structure::isNonExtensibleOrHasNonConfigurableProperties const):
(JSC::Structure::bitField const):
* Source/JavaScriptCore/runtime/StructureInlines.h:
(JSC::Structure::add):
(JSC::Structure::attributeChange):
* Source/JavaScriptCore/wasm/js/WebAssemblyGCObjectBase.h:
* Source/WebCore/bindings/js/JSDOMWindowProperties.h:

Canonical link: https://commits.webkit.org/263443@main

a2d277d

Misc iOS, tvOS & watchOS macOS Linux Windows
❌ πŸ§ͺ style βœ… πŸ›  ios βœ… πŸ›  mac βœ… πŸ›  wpe βœ… πŸ›  wincairo
βœ… πŸ§ͺ bindings βœ… πŸ›  ios-sim βœ… πŸ›  mac-AS-debug βœ… πŸ§ͺ wpe-wk2
βœ… πŸ§ͺ webkitperl βœ… πŸ§ͺ ios-wk2 βœ… πŸ§ͺ api-mac βœ… πŸ›  gtk
βœ… πŸ§ͺ ios-wk2-wpt βœ… πŸ§ͺ mac-wk1   πŸ§ͺ gtk-wk2
βœ… πŸ›  πŸ§ͺ jsc βœ… πŸ§ͺ api-ios βœ… πŸ§ͺ mac-wk2 βœ… πŸ§ͺ api-gtk
βœ… πŸ›  πŸ§ͺ jsc-arm64 βœ… πŸ›  tv βœ… πŸ§ͺ mac-AS-debug-wk2 βœ… πŸ›  jsc-armv7
βœ… πŸ›  tv-sim βœ… πŸ§ͺ jsc-armv7-tests
βœ… πŸ›  watch βœ… πŸ›  jsc-mips
βœ… πŸ›  πŸ§ͺ unsafe-merge βœ… πŸ›  watch-sim βœ… πŸ§ͺ jsc-mips-tests

@shvaikalesh shvaikalesh requested review from cdumez and a team as code owners April 19, 2023 18:03
@shvaikalesh shvaikalesh self-assigned this Apr 19, 2023
@shvaikalesh shvaikalesh added the JavaScriptCore For bugs in JavaScriptCore, the JS engine used by WebKit, other than kxmlcore issues. label Apr 19, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 19, 2023
@shvaikalesh shvaikalesh removed the merging-blocked Applied to prevent a change from being merged label Apr 20, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 20, 2023
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp Outdated Show resolved Hide resolved
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp Outdated Show resolved Hide resolved
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h Outdated Show resolved Hide resolved
@shvaikalesh shvaikalesh removed the merging-blocked Applied to prevent a change from being merged label Apr 21, 2023
@shvaikalesh shvaikalesh changed the title [JSC] Skip result validation in some of the ProxyObject's traps for most targets [JSC] Skip ProxyObject's ICs trap result validation in the common case Apr 21, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 21, 2023
@shvaikalesh shvaikalesh removed the merging-blocked Applied to prevent a change from being merged label Apr 21, 2023
Source/JavaScriptCore/dfg/DFGNode.h Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGClobberize.h Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp Outdated Show resolved Hide resolved
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 22, 2023
@shvaikalesh shvaikalesh removed the merging-blocked Applied to prevent a change from being merged label Apr 23, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 23, 2023
Source/JavaScriptCore/dfg/DFGClobberize.h Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGClobberize.h Outdated Show resolved Hide resolved
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp Outdated Show resolved Hide resolved
Source/JavaScriptCore/runtime/Structure.h Show resolved Hide resolved
@shvaikalesh shvaikalesh removed the merging-blocked Applied to prevent a change from being merged label Apr 25, 2023
Copy link
Member

@Constellation Constellation left a comment

Choose a reason for hiding this comment

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

r=me with comments

Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp Outdated Show resolved Hide resolved
@shvaikalesh shvaikalesh added the unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing label Apr 27, 2023
https://bugs.webkit.org/show_bug.cgi?id=255661
<rdar://problem/108269769>

Reviewed by Yusuke Suzuki.

Invariants of the essential internal methods [1] are enforced for all objects, both ordinary and exotic,
to avoid them having unpredictable behavior that negatively impacts developer experience and creates
security issues.

Enforcing them for Proxy objects is especially cumbersome, given traps are userland code, and quite
expensive performance-wise: invariants invalidation requires checking property attributes,
which currently requires JS => C++ call.

After carefully analyzing all the invariants of the all internal methods, it was concluded that if
[[ProxyTarget]] is an ordinary extensible object without non-configurable properties, validation of
the invariants can be safely (non-observably) skipped.

trap name                   IC    attributes checked
========================    ==    ==================================
getOwnPropertyDescriptor    no    non-configurable || non-extensible
defineProperty              no    non-configurable || non-extensible
has                        yes    non-configurable || non-extensible
get                        yes    non-configurable && (non-writable || accessor)
set                        yes    non-configurable && (non-writable || accessor)
deleteProperty           maybe    non-configurable || non-extensible
ownKeys                     no    non-configurable || non-extensible

This change:

  1. Introduces OverridesIsExtensible out-of-line type info flag to tighten Structure's didPreventExtensions()
     flag, making it more pessimistic: it being `true` doesn't necessarily mean the structure is non-extensible,
     but the reverse of it being `false` can be safely relied on.

     It's not a completely new approach: WebKit#11843 does the same thing.

     This change was proved not to introduce bugs since Structure's didPreventExtensions() flag is currently
     checked only for objects without isExtensible() override.

  2. Introduces hasNonConfigurableProperties and hasNonConfigurableReadOnlyOrGetterSetterProperties Structure
     flags that are also pessimistic.

     When inferring their initial values, we do extra care not to be overly pessimistic for objects except
     JSFinalObject by checking overridesGetOwnPropertySlot() so that userland code, which heavily uses
     Map / Set as [[ProxyTarget]], more performant, while making an exception for JSArray's non-configurable
     non-structure "length" property.

     While there is existing hasReadOnlyOrGetterSetterPropertiesExcludingProto flag, we can't combine it with
     hasNonConfigurableProperties because MobX installs a non-writable administration property even on arrays,
     which have non-configurable "length" already, so combining them would disable optimization for JSArray.

  3. Introduces HasStructureWithFlags bytecode op to be utilized in ProxyObject IC helpers to skip trap result
     validation for most of ProxyObject's targets. In the follow-ups, this approach will be expanded to C++ code.

Currently, due to lack of proper profiling of GetInternalField results, constant folding for HasStructureWithFlags
doesn't work in microbenchmarks.

                                   ToT                      patch

proxy-has-miss              159.9825+-2.4190     ^     43.1435+-0.5994        ^ definitely 3.7082x faster
proxy-set-miss-handler      158.7617+-2.3615     ^    139.2158+-1.9197        ^ definitely 1.1404x faster
proxy-set                   125.5967+-1.3678     ^     14.4533+-0.1739        ^ definitely 8.6898x faster
proxy-get                   115.8061+-2.2413     ^     14.2136+-0.1138        ^ definitely 8.1475x faster

<geometric>                 138.5985+-0.9526     ^     33.3237+-0.2255        ^ definitely 4.1592x faster

[1]: https://tc39.es/ecma262/#sec-invariants-of-the-essential-internal-methods

* Source/JavaScriptCore/builtins/BuiltinNames.h:
* Source/JavaScriptCore/builtins/ProxyHelpers.js:
(linkTimeConstant.performProxyObjectHas):
(linkTimeConstant.performProxyObjectGet):
(linkTimeConstant.performProxyObjectSetSloppy):
(linkTimeConstant.performProxyObjectSetStrict):
* Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h:
* Source/JavaScriptCore/bytecode/BytecodeList.rb:
* Source/JavaScriptCore/bytecode/BytecodeUseDef.cpp:
(JSC::computeUsesForBytecodeIndexImpl):
(JSC::computeDefsForBytecodeIndexImpl):
* Source/JavaScriptCore/bytecode/LinkTimeConstant.h:
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitHasStructureWithFlags):
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h:
* Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp:
(JSC::BytecodeIntrinsicNode::emit_intrinsic_mustValidateResultOfProxyGetAndSetTraps):
(JSC::BytecodeIntrinsicNode::emit_intrinsic_mustValidateResultOfProxyTrapsExceptGetAndSet):
* Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* Source/JavaScriptCore/dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* Source/JavaScriptCore/dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* Source/JavaScriptCore/dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* Source/JavaScriptCore/dfg/DFGHeapLocation.cpp:
(WTF::printInternal):
* Source/JavaScriptCore/dfg/DFGHeapLocation.h:
* Source/JavaScriptCore/dfg/DFGNode.h:
(JSC::DFG::Node::structureFlags):
* Source/JavaScriptCore/dfg/DFGNodeType.h:
* Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp:
* Source/JavaScriptCore/dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp:
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h:
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* Source/JavaScriptCore/ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileCompareStrictEq):
* Source/JavaScriptCore/jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
* Source/JavaScriptCore/jit/JIT.h:
* Source/JavaScriptCore/jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_has_structure_with_flags):
* Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm:
* Source/JavaScriptCore/llint/LowLevelInterpreter64.asm:
* Source/JavaScriptCore/runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
* Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):
(JSC::globalFuncHandleProxySetTrapResult): Deleted.
* Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.h:
* Source/JavaScriptCore/runtime/JSTypeInfo.h:
(JSC::TypeInfo::overridesIsExtensible const):
* Source/JavaScriptCore/runtime/PropertySlot.h:
* Source/JavaScriptCore/runtime/ProxyObject.cpp:
(JSC::ProxyObject::performPut):
(JSC::ProxyObject::validatePositiveSetTrapResult):
(JSC::ProxyObject::validateSetTrapResult): Deleted.
* Source/JavaScriptCore/runtime/ProxyObject.h:
* Source/JavaScriptCore/runtime/Structure.cpp:
(JSC::Structure::validateFlags):
(JSC::Structure::Structure):
(JSC::Structure::nonPropertyTransitionSlow):
* Source/JavaScriptCore/runtime/Structure.h:
(JSC::Structure::isNonExtensibleOrHasNonConfigurableProperties const):
(JSC::Structure::bitField const):
* Source/JavaScriptCore/runtime/StructureInlines.h:
(JSC::Structure::add):
(JSC::Structure::attributeChange):
* Source/JavaScriptCore/wasm/js/WebAssemblyGCObjectBase.h:
* Source/WebCore/bindings/js/JSDOMWindowProperties.h:

Canonical link: https://commits.webkit.org/263443@main
@webkit-commit-queue
Copy link
Collaborator

Committed 263443@main (18d9fef): https://commits.webkit.org/263443@main

Reviewed commits have been landed. Closing PR #12919 and removing active labels.

@webkit-commit-queue webkit-commit-queue merged commit 18d9fef into WebKit:main Apr 27, 2023
@webkit-commit-queue webkit-commit-queue removed the unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing label Apr 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScriptCore For bugs in JavaScriptCore, the JS engine used by WebKit, other than kxmlcore issues.
Projects
None yet
5 participants