From 83d39868d226e411832f378a4fa5c53579cc4a81 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 4 Jun 2021 15:58:13 +0000 Subject: [PATCH] Optimize Function.prototype.toString https://bugs.webkit.org/show_bug.cgi?id=226418 Reviewed by Saam Barati. JSTests: * microbenchmarks/function-to-string.js: Added. (f): (C): (C.prototype.method1): (C.prototype.method2): (test): (test2): Source/JavaScriptCore: Add caching to Function.prototype.toString. This is used heavily in Speedometer2, and repeatedly recomputing a string which is a constant is costly. We cache the results of toString in all cases except for bound functions. To make this work for bound functions, we'd need to add a new field they can use for this cache. For other functions, we cache it on the executable (either NativeExecutable or FunctionExecutable). The reason we can't do this on the executable for bound functions is that all bound functions share the same executable, but individual bound functions can have different names. The reason it's valid to cache the results in general is that a function's name field can't be changed from JS code -- it's non-writable. This patch also makes Function.prototype.toString an intrinsic in the DFG/FTL. We emit code on the fast path which reads the cached value if it's present. If not, we call into the slow path, which will compute the cached value for non bound functions, or compute the result for bound functions. I added a new microbenchmark that speeds up by >35x: function-to-string 2197.5952+-30.7118 ^ 59.9861+-2.5550 ^ definitely 36.6350x faster * CMakeLists.txt: * JavaScriptCore.xcodeproj/project.pbxproj: * dfg/DFGAbstractInterpreterInlines.h: (JSC::DFG::AbstractInterpreter::executeEffects): * dfg/DFGByteCodeParser.cpp: (JSC::DFG::ByteCodeParser::handleIntrinsicCall): * dfg/DFGClobberize.h: (JSC::DFG::clobberize): * dfg/DFGDoesGC.cpp: (JSC::DFG::doesGC): * dfg/DFGFixupPhase.cpp: (JSC::DFG::FixupPhase::fixupNode): * dfg/DFGNodeType.h: * dfg/DFGOperations.cpp: (JSC::DFG::JSC_DEFINE_JIT_OPERATION): * dfg/DFGOperations.h: * dfg/DFGPredictionPropagationPhase.cpp: * dfg/DFGSafeToExecute.h: (JSC::DFG::safeToExecute): * dfg/DFGSpeculativeJIT.cpp: (JSC::DFG::getExecutable): (JSC::DFG::SpeculativeJIT::compileFunctionToString): (JSC::DFG::SpeculativeJIT::compileGetExecutable): * dfg/DFGSpeculativeJIT.h: * dfg/DFGSpeculativeJIT32_64.cpp: (JSC::DFG::SpeculativeJIT::compile): * dfg/DFGSpeculativeJIT64.cpp: (JSC::DFG::SpeculativeJIT::compile): * ftl/FTLAbstractHeapRepository.h: * ftl/FTLCapabilities.cpp: (JSC::FTL::canCompile): * ftl/FTLLowerDFGToB3.cpp: (JSC::FTL::DFG::LowerDFGToB3::compileNode): (JSC::FTL::DFG::LowerDFGToB3::getExecutable): (JSC::FTL::DFG::LowerDFGToB3::compileGetExecutable): (JSC::FTL::DFG::LowerDFGToB3::compileFunctionToString): * runtime/FunctionExecutable.cpp: (JSC::FunctionExecutable::visitChildrenImpl): (JSC::FunctionExecutable::toStringSlow): * runtime/FunctionExecutable.h: * runtime/FunctionExecutableInlines.h: (JSC::FunctionExecutable::toString): * runtime/FunctionPrototype.cpp: (JSC::FunctionPrototype::addFunctionProperties): (JSC::JSC_DEFINE_HOST_FUNCTION): * runtime/Intrinsic.cpp: (JSC::intrinsicName): * runtime/Intrinsic.h: * runtime/JSFunction.cpp: (JSC::JSFunction::toString): * runtime/JSFunction.h: * runtime/JSFunctionInlines.h: (JSC::JSFunction::asStringConcurrently const): * runtime/JSStringInlines.h: * runtime/NativeExecutable.cpp: (JSC::NativeExecutable::toStringSlow): (JSC::NativeExecutable::visitChildrenImpl): * runtime/NativeExecutable.h: Canonical link: https://commits.webkit.org/238482@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@278462 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- JSTests/ChangeLog | 16 ++++ JSTests/microbenchmarks/function-to-string.js | 40 +++++++++ Source/JavaScriptCore/CMakeLists.txt | 1 + Source/JavaScriptCore/ChangeLog | 83 +++++++++++++++++++ .../JavaScriptCore.xcodeproj/project.pbxproj | 2 +- .../dfg/DFGAbstractInterpreterInlines.h | 13 +++ .../JavaScriptCore/dfg/DFGByteCodeParser.cpp | 11 +++ Source/JavaScriptCore/dfg/DFGClobberize.h | 4 + Source/JavaScriptCore/dfg/DFGDoesGC.cpp | 1 + Source/JavaScriptCore/dfg/DFGFixupPhase.cpp | 4 + Source/JavaScriptCore/dfg/DFGNodeType.h | 1 + Source/JavaScriptCore/dfg/DFGOperations.cpp | 9 ++ Source/JavaScriptCore/dfg/DFGOperations.h | 1 + .../dfg/DFGPredictionPropagationPhase.cpp | 1 + Source/JavaScriptCore/dfg/DFGSafeToExecute.h | 1 + .../JavaScriptCore/dfg/DFGSpeculativeJIT.cpp | 53 ++++++++++-- Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h | 1 + .../dfg/DFGSpeculativeJIT32_64.cpp | 4 + .../dfg/DFGSpeculativeJIT64.cpp | 4 + .../ftl/FTLAbstractHeapRepository.h | 3 + Source/JavaScriptCore/ftl/FTLCapabilities.cpp | 1 + Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp | 69 +++++++++++++-- .../runtime/FunctionExecutable.cpp | 76 +++++++++++++++++ .../runtime/FunctionExecutable.h | 14 ++++ .../runtime/FunctionExecutableInlines.h | 8 ++ .../runtime/FunctionPrototype.cpp | 56 +------------ Source/JavaScriptCore/runtime/Intrinsic.cpp | 2 + Source/JavaScriptCore/runtime/Intrinsic.h | 1 + Source/JavaScriptCore/runtime/JSFunction.cpp | 16 ++++ Source/JavaScriptCore/runtime/JSFunction.h | 3 + .../runtime/JSFunctionInlines.h | 10 +++ .../JavaScriptCore/runtime/JSStringInlines.h | 1 + .../runtime/NativeExecutable.cpp | 27 ++++++ .../JavaScriptCore/runtime/NativeExecutable.h | 14 ++++ 34 files changed, 483 insertions(+), 68 deletions(-) create mode 100644 JSTests/microbenchmarks/function-to-string.js diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog index 84cce1e1d932..eb33cd9e1a2d 100644 --- a/JSTests/ChangeLog +++ b/JSTests/ChangeLog @@ -1,3 +1,19 @@ +2021-06-04 Tadeu Zagallo + + Optimize Function.prototype.toString + https://bugs.webkit.org/show_bug.cgi?id=226418 + + + Reviewed by Saam Barati. + + * microbenchmarks/function-to-string.js: Added. + (f): + (C): + (C.prototype.method1): + (C.prototype.method2): + (test): + (test2): + 2021-06-03 Ross Kirsling [JSC] Implement JIT ICs for InByVal diff --git a/JSTests/microbenchmarks/function-to-string.js b/JSTests/microbenchmarks/function-to-string.js new file mode 100644 index 000000000000..8852b2842b0a --- /dev/null +++ b/JSTests/microbenchmarks/function-to-string.js @@ -0,0 +1,40 @@ +function f(x, y, z) { + // comment in the body + const w = 42; + return w + x + y + z; + +} +noInline(f); + +class C { + // comment in the class + constructor() { + } + + method1() { + return "some string"; + } + + method2() { + return 42; + } +} + +function test() { + f.toString(); + C.toString(); + print.toString(); +} +noInline(test); + +for (let i = 0; i < 1e7; ++i) + test(); + +function test2(x, y, z) { + for (let i = 0; i < 1e6; ++i) { + f(x.toString(), y.toString(), z.toString(), x.toString(), y.toString()); + } +} +noInline(test2); + +test2(f, C, print); diff --git a/Source/JavaScriptCore/CMakeLists.txt b/Source/JavaScriptCore/CMakeLists.txt index 62caf6121708..653c5f33811a 100644 --- a/Source/JavaScriptCore/CMakeLists.txt +++ b/Source/JavaScriptCore/CMakeLists.txt @@ -943,6 +943,7 @@ set(JavaScriptCore_PRIVATE_FRAMEWORK_HEADERS runtime/JSArrayBufferViewInlines.h runtime/JSArrayIterator.h runtime/JSBigInt.h + runtime/JSBoundFunction.h runtime/JSCConfig.h runtime/JSCInlines.h runtime/JSCJSValue.h diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog index e85d17063140..1583e5fa5135 100644 --- a/Source/JavaScriptCore/ChangeLog +++ b/Source/JavaScriptCore/ChangeLog @@ -1,3 +1,86 @@ +2021-06-04 Tadeu Zagallo + + Optimize Function.prototype.toString + https://bugs.webkit.org/show_bug.cgi?id=226418 + + + Reviewed by Saam Barati. + + Add caching to Function.prototype.toString. This is used heavily in Speedometer2, and repeatedly recomputing a + string which is a constant is costly. We cache the results of toString in all cases except for bound functions. + To make this work for bound functions, we'd need to add a new field they can use for this cache. For other + functions, we cache it on the executable (either NativeExecutable or FunctionExecutable). The reason we can't + do this on the executable for bound functions is that all bound functions share the same executable, but + individual bound functions can have different names. The reason it's valid to cache the results in general is that a + function's name field can't be changed from JS code -- it's non-writable. + + This patch also makes Function.prototype.toString an intrinsic in the DFG/FTL. We emit code on the fast path + which reads the cached value if it's present. If not, we call into the slow path, which will compute + the cached value for non bound functions, or compute the result for bound functions. + + I added a new microbenchmark that speeds up by >35x: + + function-to-string 2197.5952+-30.7118 ^ 59.9861+-2.5550 ^ definitely 36.6350x faster + + * CMakeLists.txt: + * JavaScriptCore.xcodeproj/project.pbxproj: + * dfg/DFGAbstractInterpreterInlines.h: + (JSC::DFG::AbstractInterpreter::executeEffects): + * dfg/DFGByteCodeParser.cpp: + (JSC::DFG::ByteCodeParser::handleIntrinsicCall): + * dfg/DFGClobberize.h: + (JSC::DFG::clobberize): + * dfg/DFGDoesGC.cpp: + (JSC::DFG::doesGC): + * dfg/DFGFixupPhase.cpp: + (JSC::DFG::FixupPhase::fixupNode): + * dfg/DFGNodeType.h: + * dfg/DFGOperations.cpp: + (JSC::DFG::JSC_DEFINE_JIT_OPERATION): + * dfg/DFGOperations.h: + * dfg/DFGPredictionPropagationPhase.cpp: + * dfg/DFGSafeToExecute.h: + (JSC::DFG::safeToExecute): + * dfg/DFGSpeculativeJIT.cpp: + (JSC::DFG::getExecutable): + (JSC::DFG::SpeculativeJIT::compileFunctionToString): + (JSC::DFG::SpeculativeJIT::compileGetExecutable): + * dfg/DFGSpeculativeJIT.h: + * dfg/DFGSpeculativeJIT32_64.cpp: + (JSC::DFG::SpeculativeJIT::compile): + * dfg/DFGSpeculativeJIT64.cpp: + (JSC::DFG::SpeculativeJIT::compile): + * ftl/FTLAbstractHeapRepository.h: + * ftl/FTLCapabilities.cpp: + (JSC::FTL::canCompile): + * ftl/FTLLowerDFGToB3.cpp: + (JSC::FTL::DFG::LowerDFGToB3::compileNode): + (JSC::FTL::DFG::LowerDFGToB3::getExecutable): + (JSC::FTL::DFG::LowerDFGToB3::compileGetExecutable): + (JSC::FTL::DFG::LowerDFGToB3::compileFunctionToString): + * runtime/FunctionExecutable.cpp: + (JSC::FunctionExecutable::visitChildrenImpl): + (JSC::FunctionExecutable::toStringSlow): + * runtime/FunctionExecutable.h: + * runtime/FunctionExecutableInlines.h: + (JSC::FunctionExecutable::toString): + * runtime/FunctionPrototype.cpp: + (JSC::FunctionPrototype::addFunctionProperties): + (JSC::JSC_DEFINE_HOST_FUNCTION): + * runtime/Intrinsic.cpp: + (JSC::intrinsicName): + * runtime/Intrinsic.h: + * runtime/JSFunction.cpp: + (JSC::JSFunction::toString): + * runtime/JSFunction.h: + * runtime/JSFunctionInlines.h: + (JSC::JSFunction::asStringConcurrently const): + * runtime/JSStringInlines.h: + * runtime/NativeExecutable.cpp: + (JSC::NativeExecutable::toStringSlow): + (JSC::NativeExecutable::visitChildrenImpl): + * runtime/NativeExecutable.h: + 2021-06-04 Michael Catanzaro Fix more GCC warnings diff --git a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj index 15cc221b122c..2dec24c8bb6b 100644 --- a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj +++ b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj @@ -1302,7 +1302,7 @@ 86ECA3FA132DF25A002B2AD7 /* DFGScoreBoard.h in Headers */ = {isa = PBXBuildFile; fileRef = 86ECA3F9132DF25A002B2AD7 /* DFGScoreBoard.h */; }; 86F3EEBD168CDE930077B92A /* ObjCCallbackFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 86F3EEB9168CCF750077B92A /* ObjCCallbackFunction.h */; }; 86F3EEBF168CDE930077B92A /* ObjcRuntimeExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 86F3EEB616855A5B0077B92A /* ObjcRuntimeExtras.h */; }; - 86FA9E92142BBB2E001773B7 /* JSBoundFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 86FA9E90142BBB2E001773B7 /* JSBoundFunction.h */; }; + 86FA9E92142BBB2E001773B7 /* JSBoundFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 86FA9E90142BBB2E001773B7 /* JSBoundFunction.h */; settings = {ATTRIBUTES = (Private, ); }; }; 8B3BF5E41E3D368B0076A87A /* AsyncGeneratorPrototype.lut.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3BF5E31E3D365A0076A87A /* AsyncGeneratorPrototype.lut.h */; }; 8B6016F61F3E3CC000F9DE6A /* AsyncFromSyncIteratorPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6016F41F3E3CC000F9DE6A /* AsyncFromSyncIteratorPrototype.h */; }; 8B9F6D561D5912FA001C739F /* IterationKind.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B9F6D551D5912FA001C739F /* IterationKind.h */; settings = {ATTRIBUTES = (Private, ); }; }; diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h index 281fa90f301a..3b61a5fd1989 100644 --- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h +++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h @@ -2855,6 +2855,19 @@ bool AbstractInterpreter::executeEffects(unsigned clobberLimi break; } + case FunctionToString: { + JSValue value = m_state.forNode(node->child1()).value(); + if (value) { + JSFunction* function = jsDynamicCast(m_vm, value); + if (JSString* asString = function->asStringConcurrently(m_vm)) { + setConstant(node, *m_graph.freeze(asString)); + break; + } + } + setForNode(node, m_vm.stringStructure.get()); + break; + } + case NumberToStringWithRadix: { JSValue radixValue = forNode(node->child2()).m_value; if (radixValue && radixValue.isInt32()) { diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index 386f5f583dbe..5cd2d8f2a36a 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -3742,6 +3742,17 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, Operand result, Intrinsic #endif } + case FunctionToStringIntrinsic: { + if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) + return false; + + insertChecks(); + Node* function = get(virtualRegisterForArgumentIncludingThis(0, registerOffset)); + Node* resultNode = addToGraph(FunctionToString, function); + setResult(resultNode); + return true; + } + default: return false; } diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.h index 01da56de18f8..0124cde87bbd 100644 --- a/Source/JavaScriptCore/dfg/DFGClobberize.h +++ b/Source/JavaScriptCore/dfg/DFGClobberize.h @@ -1818,6 +1818,10 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu RELEASE_ASSERT_NOT_REACHED(); return; } + + case FunctionToString: + def(PureValue(node)); + return; case CountExecution: case SuperSamplerBegin: diff --git a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp index c910206d8946..5100c5c7d645 100644 --- a/Source/JavaScriptCore/dfg/DFGDoesGC.cpp +++ b/Source/JavaScriptCore/dfg/DFGDoesGC.cpp @@ -290,6 +290,7 @@ bool doesGC(Graph& graph, Node* node) case DirectTailCall: case DirectTailCallInlinedCaller: case ForceOSRExit: + case FunctionToString: case GetById: case GetByIdDirect: case GetByIdDirectFlush: diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp index c46a5ff13d6f..8d1e906a5ca6 100644 --- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp @@ -1973,6 +1973,10 @@ class FixupPhase : public Phase { break; } + case FunctionToString: { + fixEdge(node->child1()); + break; + } case SetPrivateBrand: { fixEdge(node->child1()); diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h index dab3e4fa9ccb..a010b552d0a2 100644 --- a/Source/JavaScriptCore/dfg/DFGNodeType.h +++ b/Source/JavaScriptCore/dfg/DFGNodeType.h @@ -428,6 +428,7 @@ namespace JSC { namespace DFG { macro(CallNumberConstructor, NodeResultJS | NodeMustGenerate) \ macro(NumberToStringWithRadix, NodeResultJS | NodeMustGenerate) \ macro(NumberToStringWithValidRadixConstant, NodeResultJS) \ + macro(FunctionToString, NodeResultJS) \ macro(MakeRope, NodeResultJS) \ macro(InByVal, NodeResultBoolean | NodeMustGenerate) \ macro(InById, NodeResultBoolean | NodeMustGenerate) \ diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp index b1723dff9527..99ecec0122e7 100644 --- a/Source/JavaScriptCore/dfg/DFGOperations.cpp +++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp @@ -2560,6 +2560,15 @@ JSC_DEFINE_JIT_OPERATION(operationDoubleToStringWithValidRadix, char*, (JSGlobal return reinterpret_cast(numberToString(vm, value, radix)); } +JSC_DEFINE_JIT_OPERATION(operationFunctionToString, JSString*, (JSGlobalObject* globalObject, JSFunction* function)) +{ + VM& vm = globalObject->vm(); + CallFrame* callFrame = DECLARE_CALL_FRAME(vm); + JITOperationPrologueCallFrameTracer tracer(vm, callFrame); + + return function->toString(globalObject); +} + JSC_DEFINE_JIT_OPERATION(operationSingleCharacterString, JSString*, (VM* vmPointer, int32_t character)) { VM& vm = *vmPointer; diff --git a/Source/JavaScriptCore/dfg/DFGOperations.h b/Source/JavaScriptCore/dfg/DFGOperations.h index 52cdbe826616..99c39251d1f5 100644 --- a/Source/JavaScriptCore/dfg/DFGOperations.h +++ b/Source/JavaScriptCore/dfg/DFGOperations.h @@ -246,6 +246,7 @@ JSC_DECLARE_JIT_OPERATION(operationDoubleToString, char*, (JSGlobalObject*, doub JSC_DECLARE_JIT_OPERATION(operationInt32ToStringWithValidRadix, char*, (JSGlobalObject*, int32_t, int32_t)); JSC_DECLARE_JIT_OPERATION(operationInt52ToStringWithValidRadix, char*, (JSGlobalObject*, int64_t, int32_t)); JSC_DECLARE_JIT_OPERATION(operationDoubleToStringWithValidRadix, char*, (JSGlobalObject*, double, int32_t)); +JSC_DECLARE_JIT_OPERATION(operationFunctionToString, JSString*, (JSGlobalObject*, JSFunction*)); JSC_DECLARE_JIT_OPERATION(operationNormalizeMapKeyHeapBigInt, EncodedJSValue, (VM*, JSBigInt*)); JSC_DECLARE_JIT_OPERATION(operationMapHash, UCPUStrictInt32, (JSGlobalObject*, EncodedJSValue input)); diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp index ba0d3e36c1df..4329be5a5fc9 100644 --- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp @@ -1158,6 +1158,7 @@ class PredictionPropagationPhase : public Phase { case StringCharAt: case CallStringConstructor: case ToString: + case FunctionToString: case NumberToStringWithRadix: case NumberToStringWithValidRadixConstant: case MakeRope: diff --git a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h index 63aee280f4e2..02306647a5a2 100644 --- a/Source/JavaScriptCore/dfg/DFGSafeToExecute.h +++ b/Source/JavaScriptCore/dfg/DFGSafeToExecute.h @@ -273,6 +273,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node, bool igno case ToBoolean: case LogicalNot: case ToString: + case FunctionToString: case NumberToStringWithValidRadixConstant: case StrCat: case CallStringConstructor: diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp index 9059729953e6..ef2172fd0435 100644 --- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp +++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp @@ -52,6 +52,7 @@ #include "JSArrayIterator.h" #include "JSAsyncFunction.h" #include "JSAsyncGeneratorFunction.h" +#include "JSBoundFunction.h" #include "JSCInlines.h" #include "JSGeneratorFunction.h" #include "JSImmutableButterfly.h" @@ -10555,6 +10556,47 @@ void SpeculativeJIT::compileToStringOrCallStringConstructorOrStringValueOf(Node* } } +static void getExecutable(JITCompiler& jit, GPRReg functionGPR, GPRReg resultGPR) +{ + jit.loadPtr(JITCompiler::Address(functionGPR, JSFunction::offsetOfExecutableOrRareData()), resultGPR); + auto hasExecutable = jit.branchTestPtr(CCallHelpers::Zero, resultGPR, CCallHelpers::TrustedImm32(JSFunction::rareDataTag)); + jit.loadPtr(CCallHelpers::Address(resultGPR, FunctionRareData::offsetOfExecutable() - JSFunction::rareDataTag), resultGPR); + hasExecutable.link(&jit); +} + +void SpeculativeJIT::compileFunctionToString(Node* node) +{ + SpeculateCellOperand function(this, node->child1()); + GPRTemporary executable(this); + GPRTemporary result(this); + JITCompiler::JumpList slowCases; + + speculateFunction(node->child1(), function.gpr()); + + m_jit.emitLoadStructure(vm(), function.gpr(), result.gpr(), executable.gpr()); + m_jit.loadPtr(JITCompiler::Address(result.gpr(), Structure::classInfoOffset()), result.gpr()); + static_assert(std::is_final_v, "We don't handle subclasses when comparing classInfo below"); + slowCases.append(m_jit.branchPtr(CCallHelpers::Equal, result.gpr(), TrustedImmPtr(JSBoundFunction::info()))); + + getExecutable(m_jit, function.gpr(), executable.gpr()); + JITCompiler::Jump isNativeExecutable = m_jit.branch8(JITCompiler::Equal, JITCompiler::Address(executable.gpr(), JSCell::typeInfoTypeOffset()), TrustedImm32(NativeExecutableType)); + + m_jit.loadPtr(MacroAssembler::Address(executable.gpr(), FunctionExecutable::offsetOfRareData()), result.gpr()); + slowCases.append(m_jit.branchTestPtr(MacroAssembler::Zero, result.gpr())); + m_jit.loadPtr(MacroAssembler::Address(result.gpr(), FunctionExecutable::offsetOfAsStringInRareData()), result.gpr()); + JITCompiler::Jump continuation = m_jit.jump(); + + isNativeExecutable.link(&m_jit); + m_jit.loadPtr(MacroAssembler::Address(executable.gpr(), NativeExecutable::offsetOfAsString()), result.gpr()); + + continuation.link(&m_jit); + slowCases.append(m_jit.branchTestPtr(MacroAssembler::Zero, result.gpr())); + + addSlowPathGenerator(slowPathCall(slowCases, this, operationFunctionToString, result.gpr(), TrustedImmPtr::weakPointer(m_graph, m_graph.globalObjectFor(node->origin.semantic)), function.gpr())); + + cellResult(result.gpr(), node); +} + void SpeculativeJIT::compileNumberToStringWithValidRadixConstant(Node* node) { compileNumberToStringWithValidRadixConstant(node, node->validRadixConstant()); @@ -13304,14 +13346,9 @@ void SpeculativeJIT::compileGetExecutable(Node* node) { SpeculateCellOperand function(this, node->child1()); GPRTemporary result(this, Reuse, function); - GPRReg functionGPR = function.gpr(); - GPRReg resultGPR = result.gpr(); - speculateCellType(node->child1(), functionGPR, SpecFunction, JSFunctionType); - m_jit.loadPtr(JITCompiler::Address(functionGPR, JSFunction::offsetOfExecutableOrRareData()), resultGPR); - auto hasExecutable = m_jit.branchTestPtr(CCallHelpers::Zero, resultGPR, CCallHelpers::TrustedImm32(JSFunction::rareDataTag)); - m_jit.loadPtr(CCallHelpers::Address(resultGPR, FunctionRareData::offsetOfExecutable() - JSFunction::rareDataTag), resultGPR); - hasExecutable.link(&m_jit); - cellResult(resultGPR, node); + speculateFunction(node->child1(), function.gpr()); + getExecutable(m_jit, function.gpr(), result.gpr()); + cellResult(result.gpr(), node); } void SpeculativeJIT::compileGetGetter(Node* node) diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h index 74c15e697e2c..cf646a5afb61 100644 --- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h +++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h @@ -1227,6 +1227,7 @@ class SpeculativeJIT { void emitSwitch(Node*); void compileToStringOrCallStringConstructorOrStringValueOf(Node*); + void compileFunctionToString(Node*); void compileNumberToStringWithRadix(Node*); void compileNumberToStringWithValidRadixConstant(Node*); void compileNumberToStringWithValidRadixConstant(Node*, int32_t radix); diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp index 63ca3fd2e1ae..17b48accd5ee 100644 --- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp +++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp @@ -3205,6 +3205,10 @@ void SpeculativeJIT::compile(Node* node) compileToStringOrCallStringConstructorOrStringValueOf(node); break; } + + case FunctionToString: + compileFunctionToString(node); + break; case NewStringObject: { compileNewStringObject(node); diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp index cd9d17a64877..772d4250aa73 100644 --- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp +++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp @@ -3790,6 +3790,10 @@ void SpeculativeJIT::compile(Node* node) compileToStringOrCallStringConstructorOrStringValueOf(node); break; } + + case FunctionToString: + compileFunctionToString(node); + break; case NewStringObject: { compileNewStringObject(node); diff --git a/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h b/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h index b06884b528d2..2e2f07dfc7e8 100644 --- a/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h +++ b/Source/JavaScriptCore/ftl/FTLAbstractHeapRepository.h @@ -77,6 +77,8 @@ namespace JSC { namespace FTL { macro(DirectArguments_minCapacity, DirectArguments::offsetOfMinCapacity()) \ macro(DirectArguments_mappedArguments, DirectArguments::offsetOfMappedArguments()) \ macro(DirectArguments_modifiedArgumentsDescriptor, DirectArguments::offsetOfModifiedArgumentsDescriptor()) \ + macro(FunctionExecutable_rareData, FunctionExecutable::offsetOfRareData()) \ + macro(FunctionExecutableRareData_asString, FunctionExecutable::offsetOfAsStringInRareData()) \ macro(FunctionRareData_allocator, FunctionRareData::offsetOfObjectAllocationProfile() + ObjectAllocationProfileWithPrototype::offsetOfAllocator()) \ macro(FunctionRareData_structure, FunctionRareData::offsetOfObjectAllocationProfile() + ObjectAllocationProfileWithPrototype::offsetOfStructure()) \ macro(FunctionRareData_prototype, FunctionRareData::offsetOfObjectAllocationProfile() + ObjectAllocationProfileWithPrototype::offsetOfPrototype()) \ @@ -119,6 +121,7 @@ namespace JSC { namespace FTL { macro(JSRopeString_fiber2, JSRopeString::offsetOfFiber2()) \ macro(JSScope_next, JSScope::offsetOfNext()) \ macro(JSSymbolTableObject_symbolTable, JSSymbolTableObject::offsetOfSymbolTable()) \ + macro(NativeExecutable_asString, NativeExecutable::offsetOfAsString()) \ macro(RegExpObject_regExpAndLastIndexIsNotWritableFlag, RegExpObject::offsetOfRegExpAndLastIndexIsNotWritableFlag()) \ macro(RegExpObject_lastIndex, RegExpObject::offsetOfLastIndex()) \ macro(ShadowChicken_Packet_callee, OBJECT_OFFSETOF(ShadowChicken::Packet, callee)) \ diff --git a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp index 1988e32a75ac..1a4c4039c585 100644 --- a/Source/JavaScriptCore/ftl/FTLCapabilities.cpp +++ b/Source/JavaScriptCore/ftl/FTLCapabilities.cpp @@ -217,6 +217,7 @@ inline CapabilityLevel canCompile(Node* node) case ToNumber: case ToNumeric: case ToString: + case FunctionToString: case ToObject: case CallObjectConstructor: case CallStringConstructor: diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp index d78d7e3aa92b..e052233ae52a 100644 --- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp +++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp @@ -77,6 +77,7 @@ #include "JSAsyncFunction.h" #include "JSAsyncGenerator.h" #include "JSAsyncGeneratorFunction.h" +#include "JSBoundFunction.h" #include "JSCInlines.h" #include "JSGenerator.h" #include "JSGeneratorFunction.h" @@ -1196,6 +1197,9 @@ class LowerDFGToB3 { case StringValueOf: compileToStringOrCallStringConstructorOrStringValueOf(); break; + case FunctionToString: + compileFunctionToString(); + break; case ToPrimitive: compileToPrimitive(); break; @@ -3774,14 +3778,12 @@ class LowerDFGToB3 { speculate(BadIdent, noValue(), nullptr, m_out.notEqual(stringImpl, m_out.constIntPtr(uid))); } - void compileGetExecutable() + LValue getExecutable(LValue function) { LBasicBlock continuation = m_out.newBlock(); LBasicBlock hasRareData = m_out.newBlock(); - LValue cell = lowCell(m_node->child1()); - speculateFunction(m_node->child1(), cell); - LValue rareDataTags = m_out.loadPtr(cell, m_heaps.JSFunction_executableOrRareData); + LValue rareDataTags = m_out.loadPtr(function, m_heaps.JSFunction_executableOrRareData); ValueFromBlock fastExecutable = m_out.anchor(rareDataTags); m_out.branch(m_out.testIsZeroPtr(rareDataTags, m_out.constIntPtr(JSFunction::rareDataTag)), unsure(continuation), unsure(hasRareData)); @@ -3791,7 +3793,15 @@ class LowerDFGToB3 { m_out.jump(continuation); m_out.appendTo(continuation, lastNext); - setJSValue(m_out.phi(pointerType(), fastExecutable, slowExecutable)); + return m_out.phi(pointerType(), fastExecutable, slowExecutable); + } + + void compileGetExecutable() + { + LValue cell = lowCell(m_node->child1()); + speculateFunction(m_node->child1(), cell); + LValue executable = getExecutable(cell); + setJSValue(executable); } void compileArrayify() @@ -8220,6 +8230,55 @@ class LowerDFGToB3 { break; } } + + void compileFunctionToString() + { + JSGlobalObject* globalObject = m_graph.globalObjectFor(m_origin.semantic); + + LBasicBlock notBoundFunctionCase = m_out.newBlock(); + LBasicBlock functionExecutableCase = m_out.newBlock(); + LBasicBlock nativeExecutableCase = m_out.newBlock(); + LBasicBlock testPtr = m_out.newBlock(); + LBasicBlock hasRareData = m_out.newBlock(); + LBasicBlock slowCase = m_out.newBlock(); + LBasicBlock continuation = m_out.newBlock(); + + LValue function = lowCell(m_node->child1()); + speculateFunction(m_node->child1(), function); + + LValue structure = loadStructure(function); + LValue classInfo = m_out.loadPtr(structure, m_heaps.Structure_classInfo); + static_assert(std::is_final_v, "We don't handle subclasses when comparing classInfo below"); + m_out.branch(m_out.equal(classInfo, m_out.constIntPtr(JSBoundFunction::info())), unsure(slowCase), unsure(notBoundFunctionCase)); + + LBasicBlock lastNext = m_out.appendTo(notBoundFunctionCase, nativeExecutableCase); + LValue executable = getExecutable(function); + m_out.branch(isType(executable, NativeExecutableType), unsure(nativeExecutableCase), unsure(functionExecutableCase)); + + m_out.appendTo(nativeExecutableCase, functionExecutableCase); + ValueFromBlock nativeResult = m_out.anchor(m_out.loadPtr(executable, m_heaps.NativeExecutable_asString)); + m_out.jump(testPtr); + + m_out.appendTo(functionExecutableCase, testPtr); + LValue rareData = m_out.loadPtr(executable, m_heaps.FunctionExecutable_rareData); + m_out.branch(m_out.notNull(rareData), usually(hasRareData), rarely(slowCase)); + + m_out.appendTo(hasRareData, slowCase); + ValueFromBlock functionResult = m_out.anchor(m_out.loadPtr(rareData, m_heaps.FunctionExecutableRareData_asString)); + m_out.jump(testPtr); + + m_out.appendTo(testPtr, continuation); + LValue asString = m_out.phi(pointerType(), nativeResult, functionResult); + ValueFromBlock fastResult = m_out.anchor(asString); + m_out.branch(m_out.notNull(asString), usually(continuation), rarely(slowCase)); + + m_out.appendTo(slowCase, continuation); + ValueFromBlock slowResult = m_out.anchor(vmCall(pointerType(), operationFunctionToString, weakPointer(globalObject), function)); + m_out.jump(continuation); + + m_out.appendTo(continuation, lastNext); + setJSValue(m_out.phi(pointerType(), fastResult, slowResult)); + } void compileToPrimitive() { diff --git a/Source/JavaScriptCore/runtime/FunctionExecutable.cpp b/Source/JavaScriptCore/runtime/FunctionExecutable.cpp index 8f5cb5cded60..06fc2d196ddd 100644 --- a/Source/JavaScriptCore/runtime/FunctionExecutable.cpp +++ b/Source/JavaScriptCore/runtime/FunctionExecutable.cpp @@ -79,6 +79,7 @@ void FunctionExecutable::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_unlinkedExecutable); if (RareData* rareData = thisObject->m_rareData.get()) { visitor.append(rareData->m_cachedPolyProtoStructure); + visitor.append(rareData->m_asString); if (TemplateObjectMap* map = rareData->m_templateObjectMap.get()) { Locker locker { thisObject->cellLock() }; for (auto& entry : *map) @@ -116,6 +117,81 @@ FunctionExecutable::RareData& FunctionExecutable::ensureRareDataSlow() return *m_rareData; } +JSString* FunctionExecutable::toStringSlow(JSGlobalObject* globalObject) +{ + VM& vm = getVM(globalObject); + ASSERT(m_rareData && !m_rareData->m_asString); + + auto throwScope = DECLARE_THROW_SCOPE(vm); + + const auto& cache = [&](JSString* asString) { + WTF::storeStoreFence(); + m_rareData->m_asString.set(vm, this, asString); + return asString; + }; + + const auto& cacheIfNoException = [&](JSValue value) -> JSString* { + RETURN_IF_EXCEPTION(throwScope, nullptr); + return cache(::JSC::asString(value)); + }; + + if (isBuiltinFunction()) + return cacheIfNoException(jsMakeNontrivialString(globalObject, "function ", name().string(), "() {\n [native code]\n}")); + + if (isClass()) + return cache(jsString(vm, classSource().view().toString())); + + String functionHeader; + switch (parseMode()) { + case SourceParseMode::GeneratorWrapperFunctionMode: + case SourceParseMode::GeneratorWrapperMethodMode: + functionHeader = "function* "; + break; + + case SourceParseMode::NormalFunctionMode: + case SourceParseMode::GetterMode: + case SourceParseMode::SetterMode: + case SourceParseMode::MethodMode: + case SourceParseMode::ProgramMode: + case SourceParseMode::ModuleAnalyzeMode: + case SourceParseMode::ModuleEvaluateMode: + case SourceParseMode::GeneratorBodyMode: + case SourceParseMode::AsyncGeneratorBodyMode: + case SourceParseMode::AsyncFunctionBodyMode: + case SourceParseMode::AsyncArrowFunctionBodyMode: + functionHeader = "function "; + break; + + case SourceParseMode::ArrowFunctionMode: + case SourceParseMode::ClassFieldInitializerMode: + functionHeader = ""; + break; + + case SourceParseMode::AsyncFunctionMode: + case SourceParseMode::AsyncMethodMode: + functionHeader = "async function "; + break; + + case SourceParseMode::AsyncArrowFunctionMode: + functionHeader = "async "; + break; + + case SourceParseMode::AsyncGeneratorWrapperFunctionMode: + case SourceParseMode::AsyncGeneratorWrapperMethodMode: + functionHeader = "async function* "; + break; + } + + StringView src = source().provider()->getRange( + parametersStartOffset(), + parametersStartOffset() + source().length()); + + String name = this->name().string(); + if (name == vm.propertyNames->starDefaultPrivateName.string()) + name = emptyString(); + return cacheIfNoException(jsMakeNontrivialString(globalObject, functionHeader, name, src)); +} + void FunctionExecutable::overrideInfo(const FunctionOverrideInfo& overrideInfo) { auto& rareData = ensureRareData(); diff --git a/Source/JavaScriptCore/runtime/FunctionExecutable.h b/Source/JavaScriptCore/runtime/FunctionExecutable.h index 223ac72624f2..a586ae23e96d 100644 --- a/Source/JavaScriptCore/runtime/FunctionExecutable.h +++ b/Source/JavaScriptCore/runtime/FunctionExecutable.h @@ -285,6 +285,17 @@ class FunctionExecutable final : public ScriptExecutable { void finalizeUnconditionally(VM&); + JSString* toString(JSGlobalObject*); + JSString* asStringConcurrently() const + { + if (!m_rareData) + return nullptr; + return m_rareData->m_asString.get(); + } + + static inline ptrdiff_t offsetOfRareData() { return OBJECT_OFFSETOF(FunctionExecutable, m_rareData); } + static inline ptrdiff_t offsetOfAsStringInRareData() { return OBJECT_OFFSETOF(RareData, m_asString); } + private: friend class ExecutableBase; FunctionExecutable(VM&, const SourceCode&, UnlinkedFunctionExecutable*, Intrinsic, bool isInsideOrdinaryFunction); @@ -304,6 +315,7 @@ class FunctionExecutable final : public ScriptExecutable { unsigned m_typeProfilingEndOffset { UINT_MAX }; std::unique_ptr m_templateObjectMap; WriteBarrier m_cachedPolyProtoStructure; + WriteBarrier m_asString; }; RareData& ensureRareData() @@ -314,6 +326,8 @@ class FunctionExecutable final : public ScriptExecutable { } RareData& ensureRareDataSlow(); + JSString* toStringSlow(JSGlobalObject*); + // FIXME: We can merge rareData pointer and top-level executable pointer. First time, setting parent. // If RareData is required, materialize RareData, swap it, and store top-level executable pointer inside RareData. // https://bugs.webkit.org/show_bug.cgi?id=197625 diff --git a/Source/JavaScriptCore/runtime/FunctionExecutableInlines.h b/Source/JavaScriptCore/runtime/FunctionExecutableInlines.h index 45299c336f85..c9a0b1224043 100644 --- a/Source/JavaScriptCore/runtime/FunctionExecutableInlines.h +++ b/Source/JavaScriptCore/runtime/FunctionExecutableInlines.h @@ -35,5 +35,13 @@ inline void FunctionExecutable::finalizeUnconditionally(VM& vm) m_singleton.finalizeUnconditionally(vm); } +JSString* FunctionExecutable::toString(JSGlobalObject* globalObject) +{ + RareData& rareData = ensureRareData(); + if (!rareData.m_asString) + return toStringSlow(globalObject); + return rareData.m_asString.get(); +} + } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/FunctionPrototype.cpp b/Source/JavaScriptCore/runtime/FunctionPrototype.cpp index 14f373a8fa03..df25fc069f47 100644 --- a/Source/JavaScriptCore/runtime/FunctionPrototype.cpp +++ b/Source/JavaScriptCore/runtime/FunctionPrototype.cpp @@ -54,8 +54,7 @@ void FunctionPrototype::finishCreation(VM& vm, const String& name) void FunctionPrototype::addFunctionProperties(VM& vm, JSGlobalObject* globalObject, JSFunction** callFunction, JSFunction** applyFunction, JSFunction** hasInstanceSymbolFunction) { - JSFunction* toStringFunction = JSFunction::create(vm, globalObject, 0, vm.propertyNames->toString.string(), functionProtoFuncToString); - putDirectWithoutTransition(vm, vm.propertyNames->toString, toStringFunction, static_cast(PropertyAttribute::DontEnum)); + JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().toStringPublicName(), functionProtoFuncToString, static_cast(PropertyAttribute::DontEnum), 0, FunctionToStringIntrinsic); *applyFunction = putDirectBuiltinFunctionWithoutTransition(vm, globalObject, vm.propertyNames->builtinNames().applyPublicName(), functionPrototypeApplyCodeGenerator(vm), static_cast(PropertyAttribute::DontEnum)); *callFunction = putDirectBuiltinFunctionWithoutTransition(vm, globalObject, vm.propertyNames->builtinNames().callPublicName(), functionPrototypeCallCodeGenerator(vm), static_cast(PropertyAttribute::DontEnum)); @@ -81,58 +80,7 @@ JSC_DEFINE_HOST_FUNCTION(functionProtoFuncToString, (JSGlobalObject* globalObjec if (thisValue.inherits(vm)) { JSFunction* function = jsCast(thisValue); Integrity::auditStructureID(vm, function->structureID()); - if (function->isHostOrBuiltinFunction()) - RELEASE_AND_RETURN(scope, JSValue::encode(jsMakeNontrivialString(globalObject, "function ", function->name(vm), "() {\n [native code]\n}"))); - - FunctionExecutable* executable = function->jsExecutable(); - if (executable->isClass()) - return JSValue::encode(jsString(vm, executable->classSource().view().toString())); - - String functionHeader; - switch (executable->parseMode()) { - case SourceParseMode::GeneratorWrapperFunctionMode: - case SourceParseMode::GeneratorWrapperMethodMode: - functionHeader = "function* "; - break; - - case SourceParseMode::NormalFunctionMode: - case SourceParseMode::GetterMode: - case SourceParseMode::SetterMode: - case SourceParseMode::MethodMode: - case SourceParseMode::ProgramMode: - case SourceParseMode::ModuleAnalyzeMode: - case SourceParseMode::ModuleEvaluateMode: - case SourceParseMode::GeneratorBodyMode: - case SourceParseMode::AsyncGeneratorBodyMode: - case SourceParseMode::AsyncFunctionBodyMode: - case SourceParseMode::AsyncArrowFunctionBodyMode: - functionHeader = "function "; - break; - - case SourceParseMode::ArrowFunctionMode: - case SourceParseMode::ClassFieldInitializerMode: - functionHeader = ""; - break; - - case SourceParseMode::AsyncFunctionMode: - case SourceParseMode::AsyncMethodMode: - functionHeader = "async function "; - break; - - case SourceParseMode::AsyncArrowFunctionMode: - functionHeader = "async "; - break; - - case SourceParseMode::AsyncGeneratorWrapperFunctionMode: - case SourceParseMode::AsyncGeneratorWrapperMethodMode: - functionHeader = "async function* "; - break; - } - - StringView source = executable->source().provider()->getRange( - executable->parametersStartOffset(), - executable->parametersStartOffset() + executable->source().length()); - RELEASE_AND_RETURN(scope, JSValue::encode(jsMakeNontrivialString(globalObject, functionHeader, function->name(vm), source))); + RELEASE_AND_RETURN(scope, JSValue::encode(function->toString(globalObject))); } if (thisValue.inherits(vm)) { diff --git a/Source/JavaScriptCore/runtime/Intrinsic.cpp b/Source/JavaScriptCore/runtime/Intrinsic.cpp index 40a9224d6859..0824b4d2f937 100644 --- a/Source/JavaScriptCore/runtime/Intrinsic.cpp +++ b/Source/JavaScriptCore/runtime/Intrinsic.cpp @@ -275,6 +275,8 @@ const char* intrinsicName(Intrinsic intrinsic) return "AtomicsXorIntrinsic"; case ParseIntIntrinsic: return "ParseIntIntrinsic"; + case FunctionToStringIntrinsic: + return "FunctionToStringIntrinsic"; case TypedArrayLengthIntrinsic: return "TypedArrayLengthIntrinsic"; case TypedArrayByteLengthIntrinsic: diff --git a/Source/JavaScriptCore/runtime/Intrinsic.h b/Source/JavaScriptCore/runtime/Intrinsic.h index 0eedcb1cc42e..0960c4b254d7 100644 --- a/Source/JavaScriptCore/runtime/Intrinsic.h +++ b/Source/JavaScriptCore/runtime/Intrinsic.h @@ -152,6 +152,7 @@ enum Intrinsic : uint8_t { AtomicsWaitIntrinsic, AtomicsXorIntrinsic, ParseIntIntrinsic, + FunctionToStringIntrinsic, // Getter intrinsics. TypedArrayLengthIntrinsic, diff --git a/Source/JavaScriptCore/runtime/JSFunction.cpp b/Source/JavaScriptCore/runtime/JSFunction.cpp index fa5aecd17c1c..416f7b75078a 100644 --- a/Source/JavaScriptCore/runtime/JSFunction.cpp +++ b/Source/JavaScriptCore/runtime/JSFunction.cpp @@ -247,6 +247,22 @@ const String JSFunction::calculatedDisplayName(VM& vm) return jsExecutable()->ecmaName().string(); } +JSString* JSFunction::toString(JSGlobalObject* globalObject) +{ + VM& vm = getVM(globalObject); + if (inherits(vm)) { + JSBoundFunction* function = jsCast(this); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue string = jsMakeNontrivialString(globalObject, "function ", function->nameString(), "() {\n [native code]\n}"); + RETURN_IF_EXCEPTION(scope, nullptr); + return asString(string); + } + + if (isHostFunction()) + return static_cast(executable())->toString(globalObject); + return jsExecutable()->toString(globalObject); +} + const SourceCode* JSFunction::sourceCode() const { if (isHostOrBuiltinFunction()) diff --git a/Source/JavaScriptCore/runtime/JSFunction.h b/Source/JavaScriptCore/runtime/JSFunction.h index bcc2e810fc6e..82aff1d2613a 100644 --- a/Source/JavaScriptCore/runtime/JSFunction.h +++ b/Source/JavaScriptCore/runtime/JSFunction.h @@ -90,6 +90,9 @@ class JSFunction : public JSCallee { JS_EXPORT_PRIVATE String name(VM&); JS_EXPORT_PRIVATE String displayName(VM&); JS_EXPORT_PRIVATE const String calculatedDisplayName(VM&); + JS_EXPORT_PRIVATE JSString* toString(JSGlobalObject*); + + JSString* asStringConcurrently(VM&) const; ExecutableBase* executable() const { diff --git a/Source/JavaScriptCore/runtime/JSFunctionInlines.h b/Source/JavaScriptCore/runtime/JSFunctionInlines.h index 2d10b3dbaea5..25d609430bb7 100644 --- a/Source/JavaScriptCore/runtime/JSFunctionInlines.h +++ b/Source/JavaScriptCore/runtime/JSFunctionInlines.h @@ -26,6 +26,7 @@ #pragma once #include "FunctionExecutable.h" +#include "JSBoundFunction.h" #include "JSFunction.h" #include "NativeExecutable.h" @@ -156,4 +157,13 @@ inline FunctionRareData* JSFunction::ensureRareDataAndAllocationProfile(JSGlobal return rareData; } +inline JSString* JSFunction::asStringConcurrently(VM& vm) const +{ + if (inherits(vm)) + return nullptr; + if (isHostFunction()) + return static_cast(executable())->asStringConcurrently(); + return jsExecutable()->asStringConcurrently(); +} + } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/JSStringInlines.h b/Source/JavaScriptCore/runtime/JSStringInlines.h index e7af0d7388a1..bc7398dbd579 100644 --- a/Source/JavaScriptCore/runtime/JSStringInlines.h +++ b/Source/JavaScriptCore/runtime/JSStringInlines.h @@ -25,6 +25,7 @@ #pragma once +#include "JSGlobalObjectInlines.h" #include "JSString.h" namespace JSC { diff --git a/Source/JavaScriptCore/runtime/NativeExecutable.cpp b/Source/JavaScriptCore/runtime/NativeExecutable.cpp index e9039a68966a..6497dc67b5ee 100644 --- a/Source/JavaScriptCore/runtime/NativeExecutable.cpp +++ b/Source/JavaScriptCore/runtime/NativeExecutable.cpp @@ -93,4 +93,31 @@ CodeBlockHash NativeExecutable::hashFor(CodeSpecializationKind kind) const return CodeBlockHash(bitwise_cast(m_constructor)); } +JSString* NativeExecutable::toStringSlow(JSGlobalObject *globalObject) +{ + VM& vm = getVM(globalObject); + + auto throwScope = DECLARE_THROW_SCOPE(vm); + + JSValue value = jsMakeNontrivialString(globalObject, "function ", name(), "() {\n [native code]\n}"); + + RETURN_IF_EXCEPTION(throwScope, nullptr); + + JSString* asString = ::JSC::asString(value); + WTF::storeStoreFence(); + m_asString.set(vm, this, asString); + return asString; +} + +template +void NativeExecutable::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + NativeExecutable* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_asString); +} + +DEFINE_VISIT_CHILDREN(NativeExecutable); + } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/NativeExecutable.h b/Source/JavaScriptCore/runtime/NativeExecutable.h index 766c96ad4049..7f123ef72339 100644 --- a/Source/JavaScriptCore/runtime/NativeExecutable.h +++ b/Source/JavaScriptCore/runtime/NativeExecutable.h @@ -67,6 +67,7 @@ class NativeExecutable final : public ExecutableBase { return OBJECT_OFFSETOF(NativeExecutable, m_constructor); } + DECLARE_VISIT_CHILDREN; static Structure* createStructure(VM&, JSGlobalObject*, JSValue proto); DECLARE_INFO; @@ -76,14 +77,27 @@ class NativeExecutable final : public ExecutableBase { const DOMJIT::Signature* signatureFor(CodeSpecializationKind) const; Intrinsic intrinsic() const; + JSString* toString(JSGlobalObject* globalObject) + { + if (!m_asString) + return toStringSlow(globalObject); + return m_asString.get(); + } + + JSString* asStringConcurrently() const { return m_asString.get(); } + static inline ptrdiff_t offsetOfAsString() { return OBJECT_OFFSETOF(NativeExecutable, m_asString); } + private: NativeExecutable(VM&, TaggedNativeFunction, TaggedNativeFunction constructor); void finishCreation(VM&, Ref&& callThunk, Ref&& constructThunk, const String& name); + JSString* toStringSlow(JSGlobalObject*); + TaggedNativeFunction m_function; TaggedNativeFunction m_constructor; String m_name; + WriteBarrier m_asString; }; } // namespace JSC