diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog index 1707a06729c1e..a58a4b0ba618d 100644 --- a/JSTests/ChangeLog +++ b/JSTests/ChangeLog @@ -1,3 +1,12 @@ +2019-03-19 Caio Lima + + [JSC] LLIntEntryPoint creates same DirectJITCode for all functions + https://bugs.webkit.org/show_bug.cgi?id=194648 + + Reviewed by Keith Miller. + + * microbenchmarks/generate-multiple-llint-entrypoints.js: Added. + 2019-03-18 Mark Lam Missing a ThrowScope release in JSObject::toString(). diff --git a/JSTests/microbenchmarks/generate-multiple-llint-entrypoints.js b/JSTests/microbenchmarks/generate-multiple-llint-entrypoints.js new file mode 100644 index 0000000000000..f7f2bc986878f --- /dev/null +++ b/JSTests/microbenchmarks/generate-multiple-llint-entrypoints.js @@ -0,0 +1,17 @@ +function assert(a, e) { + if (a !== e) + throw new Error("Expected: " + e + " but got: " + a); +} + +let n = 40000; +let arr = Array(n); + +for(let i = 0; i < n; i++) { + arr[i] = eval(`() => ${i}`); + assert(arr[i](), i); +} + +for(let i = 0; i < n; i++) { + assert(arr[i](), i); +} + diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog index 34847405de039..0fc72f85a6089 100644 --- a/Source/JavaScriptCore/ChangeLog +++ b/Source/JavaScriptCore/ChangeLog @@ -1,3 +1,117 @@ +2019-03-19 Caio Lima + + [JSC] LLIntEntryPoint creates same DirectJITCode for all functions + https://bugs.webkit.org/show_bug.cgi?id=194648 + + Reviewed by Keith Miller. + + 1. Making LLIntThunks singleton. + + Motivation: Former implementation has one LLIntThunk per type per VM. + However, the generated code for every kind of thunk is essentially the + same and we end up wasting memory (right now jitAllocationGranule = 32 bytes) + when we have 2 or more VM instantiated. Turn these thunks into + singleton will avoid such wasting. + + Tradeoff: This change comes with a price, because we will keep thunks + allocated even when there is no VM instantiated. Considering WebCore use case, + the situation of having no VM instantiated is uncommon, since once a + VM is created through `commomVM()`, it will never be destroyed. Given + that, this change does not impact the overall memory comsumption of + WebCore/JSC. It also doesn't impact memory footprint, since thunks are + generated lazily (see results below). + + Since we are keeping a static `MacroAssemblerCodeRef`, + we have the assurance that JITed code will never be deallocated, + given it is being pointed by `RefPtr m_executableMemory`. + To understand why we decided to make LLIntThunks singleton instead of + removing them, please see the comment on `llint/LLIntThunks.cpp`. + + 2. Making all LLIntEntrypoints singleton + + Motivation: With singleton LLIntThunks, we also can have singleton + DirectJITCodes and NativeJITCodes for each LLIntEntrypoint type and + avoid multiple allocations of objects with the same content. + + Tradeoff: As explained before, once we allocate an entrypoint, it + will be alive until the program exits. However, the gains we can + achieve in some use cases justifies such allocations. + + As DirectJITCode and NativeJITCode are ThreadSafeRefCounted and we are using + `codeBlock->setJITCode(makeRef(*jitCode))`, their reference counter + will never be less than 1. + + 3. Memory usage analysis + + This change reduces memory usage on stress/generate-multiple-llint-entrypoints.js + by 2% and is neutral on JetStream 2. Following results were generated + running each benchmark 6 times and using 95% Student's t distribution + confidence interval. + + microbenchmarks/generate-multiple-llint-entrypoints.js (Changes uses less memory): + Mean of memory peak on ToT: 122576896 bytes (confidence interval: 67747.2316) + Mean of memory peak on Changes: 119248213.33 bytes (confidence interval: 50251.2718) + + JetStream2 (Neutral): + Mean of memory peak on ToT: 5442742272 bytes (confidence interval: 134381565.9117) + Mean of memory peak on Changes: 5384949760 bytes (confidence interval: 158413904.8352) + + 4. Performance Analysis + + This change is performance neutral on JetStream 2 and Speedometer 2. + See results below.: + + JetStream 2 (Neutral): + Mean of score on ToT: 139.58 (confidence interval: 2.44) + Mean of score on Changes: 141.46 (confidence interval: 4.24) + + Speedometer run #1 + ToT: 110 +- 2.9 + Changes: 110 +- 1.8 + + Speedometer run #2 + ToT: 110 +- 1.6 + Changes: 108 +- 2.3 + + Speedometer run #3 + ToT: 110 +- 3.0 + Changes: 110 +- 1.4 + + * jit/JSInterfaceJIT.h: + (JSC::JSInterfaceJIT::JSInterfaceJIT): + * llint/LLIntEntrypoint.cpp: + + Here we are changing the usage or DirectJITCode by NativeJITCode on cases + where there is no difference from address of calls with and without + ArithCheck. + + (JSC::LLInt::setFunctionEntrypoint): + (JSC::LLInt::setEvalEntrypoint): + (JSC::LLInt::setProgramEntrypoint): + (JSC::LLInt::setModuleProgramEntrypoint): + (JSC::LLInt::setEntrypoint): + * llint/LLIntEntrypoint.h: + * llint/LLIntThunks.cpp: + (JSC::LLInt::generateThunkWithJumpTo): + (JSC::LLInt::functionForCallEntryThunk): + (JSC::LLInt::functionForConstructEntryThunk): + (JSC::LLInt::functionForCallArityCheckThunk): + (JSC::LLInt::functionForConstructArityCheckThunk): + (JSC::LLInt::evalEntryThunk): + (JSC::LLInt::programEntryThunk): + (JSC::LLInt::moduleProgramEntryThunk): + (JSC::LLInt::functionForCallEntryThunkGenerator): Deleted. + (JSC::LLInt::functionForConstructEntryThunkGenerator): Deleted. + (JSC::LLInt::functionForCallArityCheckThunkGenerator): Deleted. + (JSC::LLInt::functionForConstructArityCheckThunkGenerator): Deleted. + (JSC::LLInt::evalEntryThunkGenerator): Deleted. + (JSC::LLInt::programEntryThunkGenerator): Deleted. + (JSC::LLInt::moduleProgramEntryThunkGenerator): Deleted. + * llint/LLIntThunks.h: + * runtime/ScriptExecutable.cpp: + (JSC::setupLLInt): + (JSC::ScriptExecutable::prepareForExecutionImpl): + 2019-03-18 Yusuke Suzuki [JSC] Add missing exception checks revealed by newly added exception checks, follow-up after r243081 diff --git a/Source/JavaScriptCore/jit/JSInterfaceJIT.h b/Source/JavaScriptCore/jit/JSInterfaceJIT.h index 5c3e3233f811c..2cf9198f40eda 100644 --- a/Source/JavaScriptCore/jit/JSInterfaceJIT.h +++ b/Source/JavaScriptCore/jit/JSInterfaceJIT.h @@ -38,7 +38,8 @@ namespace JSC { class JSInterfaceJIT : public CCallHelpers, public GPRInfo, public FPRInfo { public: - JSInterfaceJIT(VM* vm, CodeBlock* codeBlock = 0) + + JSInterfaceJIT(VM* vm = nullptr, CodeBlock* codeBlock = nullptr) : CCallHelpers(codeBlock) , m_vm(vm) { diff --git a/Source/JavaScriptCore/llint/LLIntEntrypoint.cpp b/Source/JavaScriptCore/llint/LLIntEntrypoint.cpp index 3ebb8a66685fa..5d65fb4afeeda 100644 --- a/Source/JavaScriptCore/llint/LLIntEntrypoint.cpp +++ b/Source/JavaScriptCore/llint/LLIntEntrypoint.cpp @@ -38,25 +38,39 @@ namespace JSC { namespace LLInt { -static void setFunctionEntrypoint(VM& vm, CodeBlock* codeBlock) +static void setFunctionEntrypoint(CodeBlock* codeBlock) { CodeSpecializationKind kind = codeBlock->specializationKind(); #if ENABLE(JIT) if (VM::canUseJIT()) { if (kind == CodeForCall) { - codeBlock->setJITCode( - adoptRef(*new DirectJITCode(vm.getCTIStub(functionForCallEntryThunkGenerator).retagged(), vm.getCTIStub(functionForCallArityCheckThunkGenerator).retaggedCode(), JITCode::InterpreterThunk))); + static DirectJITCode* jitCode; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + auto callRef = functionForCallEntryThunk().retagged(); + auto callArityCheckRef = functionForCallArityCheckThunk().retaggedCode(); + jitCode = new DirectJITCode(callRef, callArityCheckRef, JITCode::InterpreterThunk, JITCode::ShareAttribute::Shared); + }); + + codeBlock->setJITCode(makeRef(*jitCode)); return; } ASSERT(kind == CodeForConstruct); - codeBlock->setJITCode( - adoptRef(*new DirectJITCode(vm.getCTIStub(functionForConstructEntryThunkGenerator).retagged(), vm.getCTIStub(functionForConstructArityCheckThunkGenerator).retaggedCode(), JITCode::InterpreterThunk))); + + static DirectJITCode* jitCode; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + auto constructRef = functionForConstructEntryThunk().retagged(); + auto constructArityCheckRef = functionForConstructArityCheckThunk().retaggedCode(); + jitCode = new DirectJITCode(constructRef, constructArityCheckRef, JITCode::InterpreterThunk, JITCode::ShareAttribute::Shared); + }); + + codeBlock->setJITCode(makeRef(*jitCode)); return; } #endif // ENABLE(JIT) - UNUSED_PARAM(vm); if (kind == CodeForCall) { static DirectJITCode* jitCode; static std::once_flag onceKey; @@ -74,18 +88,21 @@ static void setFunctionEntrypoint(VM& vm, CodeBlock* codeBlock) } } -static void setEvalEntrypoint(VM& vm, CodeBlock* codeBlock) +static void setEvalEntrypoint(CodeBlock* codeBlock) { #if ENABLE(JIT) if (VM::canUseJIT()) { - MacroAssemblerCodeRef codeRef = vm.getCTIStub(evalEntryThunkGenerator).retagged(); - codeBlock->setJITCode( - adoptRef(*new DirectJITCode(codeRef, codeRef.code(), JITCode::InterpreterThunk))); + static NativeJITCode* jitCode; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + MacroAssemblerCodeRef codeRef = evalEntryThunk().retagged(); + jitCode = new NativeJITCode(codeRef, JITCode::InterpreterThunk, Intrinsic::NoIntrinsic, JITCode::ShareAttribute::Shared); + }); + codeBlock->setJITCode(makeRef(*jitCode)); return; } #endif // ENABLE(JIT) - UNUSED_PARAM(vm); static NativeJITCode* jitCode; static std::once_flag onceKey; std::call_once(onceKey, [&] { @@ -94,18 +111,21 @@ static void setEvalEntrypoint(VM& vm, CodeBlock* codeBlock) codeBlock->setJITCode(makeRef(*jitCode)); } -static void setProgramEntrypoint(VM& vm, CodeBlock* codeBlock) +static void setProgramEntrypoint(CodeBlock* codeBlock) { #if ENABLE(JIT) if (VM::canUseJIT()) { - MacroAssemblerCodeRef codeRef = vm.getCTIStub(programEntryThunkGenerator).retagged(); - codeBlock->setJITCode( - adoptRef(*new DirectJITCode(codeRef, codeRef.code(), JITCode::InterpreterThunk))); + static NativeJITCode* jitCode; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + MacroAssemblerCodeRef codeRef = programEntryThunk().retagged(); + jitCode = new NativeJITCode(codeRef, JITCode::InterpreterThunk, Intrinsic::NoIntrinsic, JITCode::ShareAttribute::Shared); + }); + codeBlock->setJITCode(makeRef(*jitCode)); return; } #endif // ENABLE(JIT) - UNUSED_PARAM(vm); static NativeJITCode* jitCode; static std::once_flag onceKey; std::call_once(onceKey, [&] { @@ -114,18 +134,21 @@ static void setProgramEntrypoint(VM& vm, CodeBlock* codeBlock) codeBlock->setJITCode(makeRef(*jitCode)); } -static void setModuleProgramEntrypoint(VM& vm, CodeBlock* codeBlock) +static void setModuleProgramEntrypoint(CodeBlock* codeBlock) { #if ENABLE(JIT) if (VM::canUseJIT()) { - MacroAssemblerCodeRef codeRef = vm.getCTIStub(moduleProgramEntryThunkGenerator).retagged(); - codeBlock->setJITCode( - adoptRef(*new DirectJITCode(codeRef, codeRef.code(), JITCode::InterpreterThunk))); + static NativeJITCode* jitCode; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + MacroAssemblerCodeRef codeRef = moduleProgramEntryThunk().retagged(); + jitCode = new NativeJITCode(codeRef, JITCode::InterpreterThunk, Intrinsic::NoIntrinsic, JITCode::ShareAttribute::Shared); + }); + codeBlock->setJITCode(makeRef(*jitCode)); return; } #endif // ENABLE(JIT) - UNUSED_PARAM(vm); static NativeJITCode* jitCode; static std::once_flag onceKey; std::call_once(onceKey, [&] { @@ -134,20 +157,20 @@ static void setModuleProgramEntrypoint(VM& vm, CodeBlock* codeBlock) codeBlock->setJITCode(makeRef(*jitCode)); } -void setEntrypoint(VM& vm, CodeBlock* codeBlock) +void setEntrypoint(CodeBlock* codeBlock) { switch (codeBlock->codeType()) { case GlobalCode: - setProgramEntrypoint(vm, codeBlock); + setProgramEntrypoint(codeBlock); return; case ModuleCode: - setModuleProgramEntrypoint(vm, codeBlock); + setModuleProgramEntrypoint(codeBlock); return; case EvalCode: - setEvalEntrypoint(vm, codeBlock); + setEvalEntrypoint(codeBlock); return; case FunctionCode: - setFunctionEntrypoint(vm, codeBlock); + setFunctionEntrypoint(codeBlock); return; } diff --git a/Source/JavaScriptCore/llint/LLIntEntrypoint.h b/Source/JavaScriptCore/llint/LLIntEntrypoint.h index 3e81720b74790..e100fb8ff306e 100644 --- a/Source/JavaScriptCore/llint/LLIntEntrypoint.h +++ b/Source/JavaScriptCore/llint/LLIntEntrypoint.h @@ -33,7 +33,7 @@ class VM; namespace LLInt { -void setEntrypoint(VM&, CodeBlock*); +void setEntrypoint(CodeBlock*); unsigned frameRegisterCountFor(CodeBlock*); diff --git a/Source/JavaScriptCore/llint/LLIntThunks.cpp b/Source/JavaScriptCore/llint/LLIntThunks.cpp index 5c194cf54d2ef..12e64fcc5f5ad 100644 --- a/Source/JavaScriptCore/llint/LLIntThunks.cpp +++ b/Source/JavaScriptCore/llint/LLIntThunks.cpp @@ -39,6 +39,7 @@ #include "ProtoCallFrame.h" #include "StackAlignment.h" #include "VM.h" +#include namespace JSC { @@ -46,9 +47,14 @@ namespace JSC { namespace LLInt { -static MacroAssemblerCodeRef generateThunkWithJumpTo(VM* vm, OpcodeID opcodeID, const char *thunkKind) +// These thunks are necessary because of nearCall used on JITed code. +// It requires that the distance from nearCall address to the destination address +// fits on 32-bits, and that's not the case of getCodeRef(llint_function_for_call_prologue) +// and others LLIntEntrypoints. + +static MacroAssemblerCodeRef generateThunkWithJumpTo(OpcodeID opcodeID, const char *thunkKind) { - JSInterfaceJIT jit(vm); + JSInterfaceJIT jit; // FIXME: there's probably a better way to do it on X86, but I'm not sure I care. LLIntCode target = LLInt::getCodeFunctionPtr(opcodeID); @@ -61,39 +67,74 @@ static MacroAssemblerCodeRef generateThunkWithJumpTo(VM* vm, Opc return FINALIZE_CODE(patchBuffer, JITThunkPtrTag, "LLInt %s prologue thunk", thunkKind); } -MacroAssemblerCodeRef functionForCallEntryThunkGenerator(VM* vm) +MacroAssemblerCodeRef functionForCallEntryThunk() { - return generateThunkWithJumpTo(vm, llint_function_for_call_prologue, "function for call"); + static LazyNeverDestroyed> codeRef; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + codeRef.construct(generateThunkWithJumpTo(llint_function_for_call_prologue, "function for call")); + }); + return codeRef; } -MacroAssemblerCodeRef functionForConstructEntryThunkGenerator(VM* vm) +MacroAssemblerCodeRef functionForConstructEntryThunk() { - return generateThunkWithJumpTo(vm, llint_function_for_construct_prologue, "function for construct"); + static LazyNeverDestroyed> codeRef; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + codeRef.construct(generateThunkWithJumpTo(llint_function_for_construct_prologue, "function for construct")); + }); + return codeRef; } -MacroAssemblerCodeRef functionForCallArityCheckThunkGenerator(VM* vm) +MacroAssemblerCodeRef functionForCallArityCheckThunk() { - return generateThunkWithJumpTo(vm, llint_function_for_call_arity_check, "function for call with arity check"); + static LazyNeverDestroyed> codeRef; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + codeRef.construct(generateThunkWithJumpTo(llint_function_for_call_arity_check, "function for call with arity check")); + }); + return codeRef; } -MacroAssemblerCodeRef functionForConstructArityCheckThunkGenerator(VM* vm) +MacroAssemblerCodeRef functionForConstructArityCheckThunk() { - return generateThunkWithJumpTo(vm, llint_function_for_construct_arity_check, "function for construct with arity check"); + static LazyNeverDestroyed> codeRef; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + codeRef.construct(generateThunkWithJumpTo(llint_function_for_construct_arity_check, "function for construct with arity check")); + }); + return codeRef; } -MacroAssemblerCodeRef evalEntryThunkGenerator(VM* vm) +MacroAssemblerCodeRef evalEntryThunk() { - return generateThunkWithJumpTo(vm, llint_eval_prologue, "eval"); + static LazyNeverDestroyed> codeRef; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + codeRef.construct(generateThunkWithJumpTo(llint_eval_prologue, "eval")); + }); + return codeRef; } -MacroAssemblerCodeRef programEntryThunkGenerator(VM* vm) +MacroAssemblerCodeRef programEntryThunk() { - return generateThunkWithJumpTo(vm, llint_program_prologue, "program"); + static LazyNeverDestroyed> codeRef; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + codeRef.construct(generateThunkWithJumpTo(llint_program_prologue, "program")); + }); + return codeRef; } -MacroAssemblerCodeRef moduleProgramEntryThunkGenerator(VM* vm) +MacroAssemblerCodeRef moduleProgramEntryThunk() { - return generateThunkWithJumpTo(vm, llint_module_program_prologue, "module_program"); + static LazyNeverDestroyed> codeRef; + static std::once_flag onceKey; + std::call_once(onceKey, [&] { + codeRef.construct(generateThunkWithJumpTo(llint_module_program_prologue, "module_program")); + }); + return codeRef; } } // namespace LLInt diff --git a/Source/JavaScriptCore/llint/LLIntThunks.h b/Source/JavaScriptCore/llint/LLIntThunks.h index 839ff1e0eaccb..e2293dfc03a6a 100644 --- a/Source/JavaScriptCore/llint/LLIntThunks.h +++ b/Source/JavaScriptCore/llint/LLIntThunks.h @@ -46,12 +46,12 @@ inline EncodedJSValue vmEntryToWasm(void* code, VM* vm, ProtoCallFrame* frame) namespace LLInt { -MacroAssemblerCodeRef functionForCallEntryThunkGenerator(VM*); -MacroAssemblerCodeRef functionForConstructEntryThunkGenerator(VM*); -MacroAssemblerCodeRef functionForCallArityCheckThunkGenerator(VM*); -MacroAssemblerCodeRef functionForConstructArityCheckThunkGenerator(VM*); -MacroAssemblerCodeRef evalEntryThunkGenerator(VM*); -MacroAssemblerCodeRef programEntryThunkGenerator(VM*); -MacroAssemblerCodeRef moduleProgramEntryThunkGenerator(VM*); +MacroAssemblerCodeRef functionForCallEntryThunk(); +MacroAssemblerCodeRef functionForConstructEntryThunk(); +MacroAssemblerCodeRef functionForCallArityCheckThunk(); +MacroAssemblerCodeRef functionForConstructArityCheckThunk(); +MacroAssemblerCodeRef evalEntryThunk(); +MacroAssemblerCodeRef programEntryThunk(); +MacroAssemblerCodeRef moduleProgramEntryThunk(); } } // namespace JSC::LLInt diff --git a/Source/JavaScriptCore/runtime/ScriptExecutable.cpp b/Source/JavaScriptCore/runtime/ScriptExecutable.cpp index 65e872d941b7f..4e1ef9a6d8cdf 100644 --- a/Source/JavaScriptCore/runtime/ScriptExecutable.cpp +++ b/Source/JavaScriptCore/runtime/ScriptExecutable.cpp @@ -385,9 +385,9 @@ CodeBlock* ScriptExecutable::newReplacementCodeBlockFor( return result; } -static void setupLLInt(VM& vm, CodeBlock* codeBlock) +static void setupLLInt(CodeBlock* codeBlock) { - LLInt::setEntrypoint(vm, codeBlock); + LLInt::setEntrypoint(codeBlock); } static void setupJIT(VM& vm, CodeBlock* codeBlock) @@ -424,7 +424,7 @@ Exception* ScriptExecutable::prepareForExecutionImpl( codeBlock->validate(); if (Options::useLLInt()) - setupLLInt(vm, codeBlock); + setupLLInt(codeBlock); else setupJIT(vm, codeBlock);