Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
44 changes: 44 additions & 0 deletions JSTests/controlFlowProfiler/function-names.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
var getFunctionRanges = $vm.getFunctionRanges;

load("./driver/driver.js");

function namedFunction() { return 1; }
function anotherFunction() { return 2; }
var arrowFunc = () => 3;

// Only call namedFunction to test executed vs not-executed.
namedFunction();

var ranges = getFunctionRanges(namedFunction);

// We should have at least the functions declared in this source.
assert(ranges.length >= 3, "Expected at least 3 function ranges, got " + ranges.length);

// Find our named functions in the ranges.
var namedFunctionRange = null;
var anotherFunctionRange = null;
var arrowFuncRange = null;

for (var i = 0; i < ranges.length; i++) {
if (ranges[i].name === "namedFunction")
namedFunctionRange = ranges[i];
else if (ranges[i].name === "anotherFunction")
anotherFunctionRange = ranges[i];
else if (ranges[i].name === "arrowFunc")
arrowFuncRange = ranges[i];
}

// Verify named functions have their names.
assert(namedFunctionRange !== null, "Should find 'namedFunction' in ranges");
assert(anotherFunctionRange !== null, "Should find 'anotherFunction' in ranges");
assert(arrowFuncRange !== null, "Should find 'arrowFunc' in ranges");

// Verify execution status.
assert(namedFunctionRange.hasExecuted === true, "namedFunction should have executed");
assert(anotherFunctionRange.hasExecuted === false, "anotherFunction should not have executed");
assert(arrowFuncRange.hasExecuted === false, "arrowFunc should not have executed");

// Verify ranges are valid (start < end).
assert(namedFunctionRange.start < namedFunctionRange.end, "namedFunction range should be valid");
assert(anotherFunctionRange.start < anotherFunctionRange.end, "anotherFunction range should be valid");
assert(arrowFuncRange.start < arrowFuncRange.end, "arrowFunc range should be valid");
4 changes: 2 additions & 2 deletions Source/JavaScriptCore/bytecode/CodeBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,15 +434,15 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink
for (size_t count = unlinkedCodeBlock->numberOfFunctionDecls(), i = 0; i < count; ++i) {
UnlinkedFunctionExecutable* unlinkedExecutable = unlinkedCodeBlock->functionDecl(i);
if (shouldUpdateFunctionHasExecutedCache)
vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd());
vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd(), unlinkedExecutable->ecmaName().string());
m_functionDecls[i].set(vm, this, unlinkedExecutable->link(vm, topLevelExecutable, ownerExecutable->source(), std::nullopt, NoIntrinsic, ownerExecutable->isInsideOrdinaryFunction()));
}

m_functionExprs = FixedVector<WriteBarrier<FunctionExecutable>>(unlinkedCodeBlock->numberOfFunctionExprs());
for (size_t count = unlinkedCodeBlock->numberOfFunctionExprs(), i = 0; i < count; ++i) {
UnlinkedFunctionExecutable* unlinkedExecutable = unlinkedCodeBlock->functionExpr(i);
if (shouldUpdateFunctionHasExecutedCache)
vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd());
vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd(), unlinkedExecutable->ecmaName().string());
m_functionExprs[i].set(vm, this, unlinkedExecutable->link(vm, topLevelExecutable, ownerExecutable->source(), std::nullopt, NoIntrinsic, ownerExecutable->isInsideOrdinaryFunction()));
}

Expand Down
2 changes: 1 addition & 1 deletion Source/JavaScriptCore/runtime/ControlFlowProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Vector<BasicBlockRange> ControlFlowProfiler::getBasicBlocksForSourceID(SourceID
}
}

const Vector<std::tuple<bool, unsigned, unsigned>>& functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID);
const auto functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID);
for (const auto& functionRange : functionRanges) {
BasicBlockRange range;
range.m_hasExecuted = std::get<0>(functionRange);
Expand Down
20 changes: 12 additions & 8 deletions Source/JavaScriptCore/runtime/FunctionHasExecutedCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,23 @@ bool FunctionHasExecutedCache::hasExecutedAtOffset(SourceID id, unsigned offset)
for (auto& pair : map) {
const FunctionRange& range = pair.key.key();
if (range.m_start <= offset && offset <= range.m_end && range.m_end - range.m_start < distance) {
hasExecuted = pair.value;
hasExecuted = pair.value.m_hasExecuted;
distance = range.m_end - range.m_start;
}
}

return hasExecuted;
}

void FunctionHasExecutedCache::insertUnexecutedRange(SourceID id, unsigned start, unsigned end)
void FunctionHasExecutedCache::insertUnexecutedRange(SourceID id, unsigned start, unsigned end, const String& functionName)
{
RangeMap& map = m_rangeMap.add(id, RangeMap { }).iterator->value;
FunctionRange range;
range.m_start = start;
range.m_end = end;
// Only insert unexecuted ranges once for a given sourceID because we may run into a situation where an executable executes, then is GCed, and then is allocated again,
// and tries to reinsert itself, claiming it has never run, but this is false because it indeed already executed.
map.add(range, false);
map.add(range, RangeValue { false, functionName });
}

void FunctionHasExecutedCache::removeUnexecutedRange(SourceID id, unsigned start, unsigned end)
Expand All @@ -73,21 +73,25 @@ void FunctionHasExecutedCache::removeUnexecutedRange(SourceID id, unsigned start
FunctionRange range;
range.m_start = start;
range.m_end = end;
map.set(range, true);
auto existingIt = map.find(range);
if (existingIt != map.end())
existingIt->value.m_hasExecuted = true;
else
map.set(range, RangeValue { true, String() });
}

Vector<std::tuple<bool, unsigned, unsigned>> FunctionHasExecutedCache::getFunctionRanges(SourceID id)
Vector<std::tuple<bool, unsigned, unsigned, String>> FunctionHasExecutedCache::getFunctionRanges(SourceID id)
{
Vector<std::tuple<bool, unsigned, unsigned>> ranges(0);
Vector<std::tuple<bool, unsigned, unsigned, String>> ranges(0);
auto iterator = m_rangeMap.find(id);
if (iterator == m_rangeMap.end())
return ranges;

RangeMap& map = iterator->value;
for (auto& pair : map) {
const FunctionRange& range = pair.key.key();
bool hasExecuted = pair.value;
ranges.append(std::tuple<bool, unsigned, unsigned>(hasExecuted, range.m_start, range.m_end));
const RangeValue& value = pair.value;
ranges.append(std::make_tuple(value.m_hasExecuted, range.m_start, range.m_end, value.m_functionName));
}

return ranges;
Expand Down
12 changes: 9 additions & 3 deletions Source/JavaScriptCore/runtime/FunctionHasExecutedCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <wtf/HashMap.h>
#include <wtf/HashTraits.h>
#include <wtf/Vector.h>
#include <wtf/text/WTFString.h>

namespace JSC {

Expand All @@ -47,13 +48,18 @@ class FunctionHasExecutedCache {
unsigned m_end;
};

struct RangeValue {
bool m_hasExecuted { false };
String m_functionName;
};

bool hasExecutedAtOffset(SourceID, unsigned offset);
void insertUnexecutedRange(SourceID, unsigned start, unsigned end);
void insertUnexecutedRange(SourceID, unsigned start, unsigned end, const String& functionName = String());
void removeUnexecutedRange(SourceID, unsigned start, unsigned end);
Vector<std::tuple<bool, unsigned, unsigned>> getFunctionRanges(SourceID);
Vector<std::tuple<bool, unsigned, unsigned, String>> getFunctionRanges(SourceID);

private:
using RangeMap = UncheckedKeyHashMap<GenericHashKey<FunctionRange>, bool>;
using RangeMap = UncheckedKeyHashMap<GenericHashKey<FunctionRange>, RangeValue>;
using SourceIDToRangeMap = UncheckedKeyHashMap<GenericHashKey<intptr_t>, RangeMap>;
SourceIDToRangeMap m_rangeMap;
};
Expand Down
3 changes: 2 additions & 1 deletion Source/JavaScriptCore/runtime/JSModuleRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ void JSModuleRecord::instantiateDeclarations(JSGlobalObject* globalObject, Modul
if (vm.typeProfiler() || vm.controlFlowProfiler()) {
vm.functionHasExecutedCache()->insertUnexecutedRange(moduleProgramExecutable->sourceID(),
unlinkedFunctionExecutable->unlinkedFunctionStart(),
unlinkedFunctionExecutable->unlinkedFunctionEnd());
unlinkedFunctionExecutable->unlinkedFunctionEnd(),
unlinkedFunctionExecutable->ecmaName().string());
}
auto* executable = unlinkedFunctionExecutable->link(vm, moduleProgramExecutable, moduleProgramExecutable->source());
SourceParseMode parseMode = executable->parseMode();
Expand Down
5 changes: 3 additions & 2 deletions Source/JavaScriptCore/runtime/ProgramExecutable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,10 @@ JSObject* ProgramExecutable::initializeGlobalProperties(VM& vm, JSGlobalObject*
globalObject->createGlobalFunctionBinding<BindingCreationContext::Global>(unlinkedFunctionExecutable->name());
RETURN_IF_EXCEPTION(throwScope, nullptr);
if (vm.typeProfiler() || vm.controlFlowProfiler()) {
vm.functionHasExecutedCache()->insertUnexecutedRange(sourceID(),
vm.functionHasExecutedCache()->insertUnexecutedRange(sourceID(),
unlinkedFunctionExecutable->unlinkedFunctionStart(),
unlinkedFunctionExecutable->unlinkedFunctionEnd());
unlinkedFunctionExecutable->unlinkedFunctionEnd(),
unlinkedFunctionExecutable->ecmaName().string());
}
}

Expand Down
38 changes: 38 additions & 0 deletions Source/JavaScriptCore/tools/JSDollarVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,7 @@ static JSC_DECLARE_HOST_FUNCTION(functionFlattenDictionaryObject);
static JSC_DECLARE_HOST_FUNCTION(functionDumpBasicBlockExecutionRanges);
static JSC_DECLARE_HOST_FUNCTION(functionHasBasicBlockExecuted);
static JSC_DECLARE_HOST_FUNCTION(functionBasicBlockExecutionCount);
static JSC_DECLARE_HOST_FUNCTION(functionGetFunctionRanges);
static JSC_DECLARE_HOST_FUNCTION(functionEnableDebuggerModeWhenIdle);
static JSC_DECLARE_HOST_FUNCTION(functionDisableDebuggerModeWhenIdle);
static JSC_DECLARE_HOST_FUNCTION(functionDeleteAllCodeWhenIdle);
Expand Down Expand Up @@ -3654,6 +3655,42 @@ JSC_DEFINE_HOST_FUNCTION(functionBasicBlockExecutionCount, (JSGlobalObject* glob
return JSValue::encode(JSValue(executionCount));
}

// $vm.getFunctionRanges(sourceFunction) returns an array of { name, hasExecuted, start, end }
// for all functions declared in the same source as the given function.
JSC_DEFINE_HOST_FUNCTION(functionGetFunctionRanges, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
DollarVMAssertScope assertScope;
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

JSValue functionValue = callFrame->argument(0);
if (!functionValue.isCallable())
return throwVMTypeError(globalObject, scope, "Expected argument to be callable"_s);
JSFunction* function = jsDynamicCast<JSFunction*>(functionValue);
if (!function)
return throwVMTypeError(globalObject, scope, "Expected argument to be a JSFunction"_s);
FunctionExecutable* executable = function->jsExecutable();

const auto functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(executable->sourceID());

JSArray* result = constructEmptyArray(globalObject, nullptr, functionRanges.size());
RETURN_IF_EXCEPTION(scope, encodedJSUndefined());

Structure* entryStructure = JSFinalObject::createStructure(vm, globalObject, globalObject->objectPrototype(), 4);
for (size_t i = 0; i < functionRanges.size(); i++) {
JSObject* entry = JSFinalObject::create(vm, entryStructure);
entry->putDirect(vm, Identifier::fromString(vm, "hasExecuted"_s), jsBoolean(std::get<0>(functionRanges[i])));
entry->putDirect(vm, Identifier::fromString(vm, "start"_s), jsNumber(std::get<1>(functionRanges[i])));
entry->putDirect(vm, Identifier::fromString(vm, "end"_s), jsNumber(std::get<2>(functionRanges[i])));
const String& name = std::get<3>(functionRanges[i]);
entry->putDirect(vm, Identifier::fromString(vm, "name"_s), name.isEmpty() ? jsEmptyString(vm) : jsString(vm, name));
result->putDirectIndex(globalObject, i, entry);
RETURN_IF_EXCEPTION(scope, encodedJSUndefined());
}

return JSValue::encode(result);
}

class DoNothingDebugger final : public Debugger {
WTF_MAKE_NONCOPYABLE(DoNothingDebugger);
WTF_MAKE_TZONE_ALLOCATED(DoNothingDebugger);
Expand Down Expand Up @@ -4438,6 +4475,7 @@ void JSDollarVM::finishCreation(VM& vm)
addFunction(vm, "dumpBasicBlockExecutionRanges"_s, functionDumpBasicBlockExecutionRanges , 0);
addFunction(vm, "hasBasicBlockExecuted"_s, functionHasBasicBlockExecuted, 2);
addFunction(vm, "basicBlockExecutionCount"_s, functionBasicBlockExecutionCount, 2);
addFunction(vm, "getFunctionRanges"_s, functionGetFunctionRanges, 1);

addFunction(vm, "enableDebuggerModeWhenIdle"_s, functionEnableDebuggerModeWhenIdle, 0);
addFunction(vm, "disableDebuggerModeWhenIdle"_s, functionDisableDebuggerModeWhenIdle, 0);
Expand Down