Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//@ runDefault("--zeroExecutableMemoryOnFree=1", "--useConcurrentJIT=0", "--thresholdForOptimizeAfterWarmUp=100", "--thresholdForFTLOptimizeAfterWarmUp=1000")
//@ runDefault("--zeroExecutableMemoryOnFree=1", "--useConcurrentJIT=0", "--thresholdForOptimizeAfterWarmUp=100", "--thresholdForFTLOptimizeAfterWarmUp=1000", "--useFTLJIT=0")

// A direct tail call to a host function must not run the call thunk from the
// caller's own JIT code. The tail call destroys the caller's frame, so nothing
// on the stack refers to the caller's CodeBlock while the host function runs.
// A jettison plus a collection inside the host function then frees the code
// that the host call returns into.
//
// The host function here is encodeURIComponent. It calls toString on its
// argument, and that hook:
// 1. adds a property to the object whose structure the caller's code watches,
// which jettisons the caller's optimized code, and
// 2. collects, which frees the jettisoned code.
// --zeroExecutableMemoryOnFree fills the freed code with zeroes, so a return
// into it crashes every time instead of once in a while.

"use strict";

const o = { f: encodeURIComponent };

let armed = false;
let hookCalls = 0;
const arg = {
toString() {
hookCalls++;
if (!armed)
return "x";
o.g = 1;
gc();
return "x";
}
};

function hot(v) { return o.f(v); }

for (let i = 0; i < testLoopCount; i++)
hot(arg);

armed = true;
for (let i = 0; i < 3; i++) {
const result = hot(arg);
if (result !== "x")
throw new Error(`expected "x", got ${result}`);
}

if (hookCalls !== testLoopCount + 3)
throw new Error(`expected ${testLoopCount + 3} hook calls, got ${hookCalls}`);
17 changes: 7 additions & 10 deletions Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,13 @@ void SpeculativeJIT::emitCall(Node* node)
nativeFunction = uncheckedDowncast<NativeExecutable>(executable)->function();
}

if (nativeFunction && !vm().isDebuggerHookInjected()) {
// A tail call must not run the thunk from this CodeBlock's own code. The tail call
// destroys this frame, so the conservative stack scan no longer finds this CodeBlock and
// cannot keep it alive (CodeBlockSet::m_currentlyExecuting). A jettison plus a collection
// inside the host function then frees the code the host call returns into. A linked direct
// tail call jumps to the executable's host call thunk, which lives as long as the VM, and
// that thunk returns to our caller.
if (nativeFunction && !isTail && !vm().isDebuggerHookInjected()) {
auto emitCallTarget = [&]() {
emitFunctionPrologue();
emitPutToCallFrameHeader(nullptr, CallFrameSlot::codeBlock);
Expand All @@ -1040,15 +1046,6 @@ void SpeculativeJIT::emitCall(Node* node)
emitFunctionEpilogue();
};

if (isTail) {
emitStoreCallSiteIndex(callSite);
CallFrameShuffler(*this, shuffleData).prepareForTailCall();
emitCallTarget();
ret();
useChildren(node);
return;
}

auto done = jump();
auto callTarget = label();
emitCallTarget();
Expand Down
17 changes: 7 additions & 10 deletions Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14129,16 +14129,13 @@ IGNORE_CLANG_WARNINGS_END
shuffleData.numParameters = jit.codeBlock()->numParameters();
shuffleData.setupCalleeSaveRegisters(state->jitCode->calleeSaveRegisters());

if (nativeFunction && !vm->isDebuggerHookInjected()) {
jit.store32(
CCallHelpers::TrustedImm32(callSiteIndex.bits()),
CCallHelpers::highWordFor(CallFrameSlot::argumentCountIncludingThis));
CallFrameShuffler(jit, shuffleData).prepareForTailCall();
emitCallTarget();
jit.ret();
return;
}

// A tail call must not run the thunk from this CodeBlock's own code. The tail
// call destroys this frame, so the conservative stack scan no longer finds
// this CodeBlock and cannot keep it alive
// (CodeBlockSet::m_currentlyExecuting). A jettison plus a collection inside
// the host function then frees the code the host call returns into. A linked
// direct tail call jumps to the executable's host call thunk, which lives as
// long as the VM, and that thunk returns to our caller.
auto* callLinkInfo = state->jitCode->common.m_directCallLinkInfos.add(semanticNodeOrigin, CallLinkInfo::UseDataIC::No, state->graph.m_codeBlock, executable);
callLinkInfo->setCallType(CallLinkInfo::DirectTailCall);
if (numAllocatedArgs > numPassedArgs)
Expand Down
Loading