Skip to content

Commit

Permalink
[JSC] LLIntEntryPoint creates same DirectJITCode for all functions
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=194648

Reviewed by Keith Miller.

JSTests:

* microbenchmarks/generate-multiple-llint-entrypoints.js: Added.

Source/JavaScriptCore:

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<JITThunkPtrTag>`,
we have the assurance that JITed code will never be deallocated,
given it is being pointed by `RefPtr<ExecutableMemoryHandle> 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):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@243136 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
ticaiolima@gmail.com committed Mar 19, 2019
1 parent fff6bd6 commit 49f5dbf
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 54 deletions.
9 changes: 9 additions & 0 deletions JSTests/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
2019-03-19 Caio Lima <ticaiolima@gmail.com>

[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 <mark.lam@apple.com>

Missing a ThrowScope release in JSObject::toString().
Expand Down
17 changes: 17 additions & 0 deletions JSTests/microbenchmarks/generate-multiple-llint-entrypoints.js
Original file line number Diff line number Diff line change
@@ -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);
}

114 changes: 114 additions & 0 deletions Source/JavaScriptCore/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,117 @@
2019-03-19 Caio Lima <ticaiolima@gmail.com>

[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<JITThunkPtrTag>`,
we have the assurance that JITed code will never be deallocated,
given it is being pointed by `RefPtr<ExecutableMemoryHandle> 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 <ysuzuki@apple.com>

[JSC] Add missing exception checks revealed by newly added exception checks, follow-up after r243081
Expand Down
3 changes: 2 additions & 1 deletion Source/JavaScriptCore/jit/JSInterfaceJIT.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
75 changes: 49 additions & 26 deletions Source/JavaScriptCore/llint/LLIntEntrypoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<JSEntryPtrTag>(), vm.getCTIStub(functionForCallArityCheckThunkGenerator).retaggedCode<JSEntryPtrTag>(), JITCode::InterpreterThunk)));
static DirectJITCode* jitCode;
static std::once_flag onceKey;
std::call_once(onceKey, [&] {
auto callRef = functionForCallEntryThunk().retagged<JSEntryPtrTag>();
auto callArityCheckRef = functionForCallArityCheckThunk().retaggedCode<JSEntryPtrTag>();
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<JSEntryPtrTag>(), vm.getCTIStub(functionForConstructArityCheckThunkGenerator).retaggedCode<JSEntryPtrTag>(), JITCode::InterpreterThunk)));

static DirectJITCode* jitCode;
static std::once_flag onceKey;
std::call_once(onceKey, [&] {
auto constructRef = functionForConstructEntryThunk().retagged<JSEntryPtrTag>();
auto constructArityCheckRef = functionForConstructArityCheckThunk().retaggedCode<JSEntryPtrTag>();
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;
Expand All @@ -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<JSEntryPtrTag> codeRef = vm.getCTIStub(evalEntryThunkGenerator).retagged<JSEntryPtrTag>();
codeBlock->setJITCode(
adoptRef(*new DirectJITCode(codeRef, codeRef.code(), JITCode::InterpreterThunk)));
static NativeJITCode* jitCode;
static std::once_flag onceKey;
std::call_once(onceKey, [&] {
MacroAssemblerCodeRef<JSEntryPtrTag> codeRef = evalEntryThunk().retagged<JSEntryPtrTag>();
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, [&] {
Expand All @@ -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<JSEntryPtrTag> codeRef = vm.getCTIStub(programEntryThunkGenerator).retagged<JSEntryPtrTag>();
codeBlock->setJITCode(
adoptRef(*new DirectJITCode(codeRef, codeRef.code(), JITCode::InterpreterThunk)));
static NativeJITCode* jitCode;
static std::once_flag onceKey;
std::call_once(onceKey, [&] {
MacroAssemblerCodeRef<JSEntryPtrTag> codeRef = programEntryThunk().retagged<JSEntryPtrTag>();
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, [&] {
Expand All @@ -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<JSEntryPtrTag> codeRef = vm.getCTIStub(moduleProgramEntryThunkGenerator).retagged<JSEntryPtrTag>();
codeBlock->setJITCode(
adoptRef(*new DirectJITCode(codeRef, codeRef.code(), JITCode::InterpreterThunk)));
static NativeJITCode* jitCode;
static std::once_flag onceKey;
std::call_once(onceKey, [&] {
MacroAssemblerCodeRef<JSEntryPtrTag> codeRef = moduleProgramEntryThunk().retagged<JSEntryPtrTag>();
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, [&] {
Expand All @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/JavaScriptCore/llint/LLIntEntrypoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class VM;

namespace LLInt {

void setEntrypoint(VM&, CodeBlock*);
void setEntrypoint(CodeBlock*);

unsigned frameRegisterCountFor(CodeBlock*);

Expand Down
Loading

0 comments on commit 49f5dbf

Please sign in to comment.