mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 950176 - Use mangled names to identify nodes in callgraph, r=bhackett
This commit is contained in:
parent
7215be83c0
commit
9bda717046
@ -90,8 +90,8 @@ function isMatchingConstructor(destructor, edge)
|
||||
var variable = callee.Variable;
|
||||
if (variable.Kind != "Func")
|
||||
return false;
|
||||
var name = variable.Name[0];
|
||||
var destructorName = destructor.Exp[0].Variable.Name[0];
|
||||
var name = readable(variable.Name[0]);
|
||||
var destructorName = readable(destructor.Exp[0].Variable.Name[0]);
|
||||
var match = destructorName.match(/^(.*?::)~(\w+)\(/);
|
||||
if (!match) {
|
||||
printErr("Unhandled destructor syntax: " + destructorName);
|
||||
|
@ -220,6 +220,11 @@ for k,v in vars(args).items():
|
||||
if args.tag and not args.buildcommand:
|
||||
args.buildcommand="build.%s" % args.tag
|
||||
|
||||
if args.jobs is not None:
|
||||
data['jobs'] = args.jobs
|
||||
if not data.get('jobs'):
|
||||
data['jobs'] = subprocess.check_output(['nproc', '--ignore=1'])
|
||||
|
||||
if args.buildcommand:
|
||||
data['buildcommand'] = args.buildcommand
|
||||
elif 'BUILD' in os.environ:
|
||||
@ -232,6 +237,9 @@ if 'ANALYZED_OBJDIR' in os.environ:
|
||||
|
||||
if 'SOURCE' in os.environ:
|
||||
data['source'] = os.environ['SOURCE']
|
||||
if not data.get('source') and data.get('sixgill_bin'):
|
||||
path = subprocess.check_output(['sh', '-c', data['sixgill_bin'] + '/xdbkeys file_source.xdb | grep jsapi.cpp'])
|
||||
data['source'] = path.replace("/js/src/jsapi.cpp", "")
|
||||
|
||||
steps = [ 'dbs',
|
||||
'callgraph',
|
||||
|
@ -24,9 +24,8 @@ var tmpfile = scriptArgs[6] || "tmp.txt";
|
||||
var gcFunctions = {};
|
||||
var text = snarf("gcFunctions.lst").split("\n");
|
||||
assert(text.pop().length == 0);
|
||||
for (var line of text) {
|
||||
gcFunctions[line] = true;
|
||||
}
|
||||
for (var line of text)
|
||||
gcFunctions[mangled(line)] = true;
|
||||
|
||||
var suppressedFunctions = {};
|
||||
var text = snarf(suppressedFunctionsFile).split("\n");
|
||||
@ -190,7 +189,7 @@ function edgeKillsVariable(edge, variable)
|
||||
break;
|
||||
|
||||
assert(callee.Variable.Kind == "Func");
|
||||
var calleeName = callee.Variable.Name[0];
|
||||
var calleeName = readable(callee.Variable.Name[0]);
|
||||
|
||||
// Constructor calls include the text 'Name::Name(' or 'Name<...>::Name('.
|
||||
var openParen = calleeName.indexOf('(');
|
||||
@ -224,11 +223,9 @@ function edgeCanGC(edge)
|
||||
if (callee.Kind == "Var") {
|
||||
var variable = callee.Variable;
|
||||
assert(variable.Kind == "Func");
|
||||
if (variable.Name[0] in gcFunctions)
|
||||
var callee = mangled(variable.Name[0]);
|
||||
if (callee in gcFunctions)
|
||||
return "'" + variable.Name[0] + "'";
|
||||
var otherName = otherDestructorName(variable.Name[0]);
|
||||
if (otherName in gcFunctions)
|
||||
return "'" + otherName + "'";
|
||||
return null;
|
||||
}
|
||||
assert(callee.Kind == "Drf");
|
||||
@ -241,8 +238,8 @@ function edgeCanGC(edge)
|
||||
return (fullFieldName in suppressedFunctions) ? null : fullFieldName;
|
||||
}
|
||||
assert(callee.Exp[0].Kind == "Var");
|
||||
var calleeName = callee.Exp[0].Variable.Name[0];
|
||||
return indirectCallCannotGC(functionName, calleeName) ? null : "*" + calleeName;
|
||||
var varName = callee.Exp[0].Variable.Name[0];
|
||||
return indirectCallCannotGC(functionName, varName) ? null : "*" + varName;
|
||||
}
|
||||
|
||||
function variableUseFollowsGC(suppressed, variable, worklist)
|
||||
@ -364,6 +361,12 @@ function variableLiveAcrossGC(suppressed, variable)
|
||||
return null;
|
||||
}
|
||||
|
||||
// An unrooted variable has its address stored in another variable via
|
||||
// assignment, or passed into a function that can GC. If the address is
|
||||
// assigned into some other variable, we can't track it to see if it is held
|
||||
// live across a GC. If it is passed into a function that can GC, then it's
|
||||
// sort of like a Handle to an unrooted location, and the callee could GC
|
||||
// before overwriting it or rooting it.
|
||||
function unsafeVariableAddressTaken(suppressed, variable)
|
||||
{
|
||||
for (var body of functionBodies) {
|
||||
@ -494,7 +497,7 @@ function processBodies(functionName)
|
||||
{
|
||||
if (!("DefineVariable" in functionBodies[0]))
|
||||
return;
|
||||
var suppressed = (functionName in suppressedFunctions);
|
||||
var suppressed = (mangled(functionName) in suppressedFunctions);
|
||||
for (var variable of functionBodies[0].DefineVariable) {
|
||||
if (variable.Variable.Kind == "Return")
|
||||
continue;
|
||||
|
@ -19,8 +19,16 @@ var ignoreIndirectCalls = {
|
||||
"nsTraceRefcntImpl.cpp:void (* leakyLogRelease)(void*, int, int)": true,
|
||||
};
|
||||
|
||||
function indirectCallCannotGC(caller, name)
|
||||
function indirectCallCannotGC(fullCaller, fullVariable)
|
||||
{
|
||||
var caller = readable(fullCaller);
|
||||
|
||||
// This is usually a simple variable name, but sometimes a full name gets
|
||||
// passed through. And sometimes that name is truncated. Examples:
|
||||
// _ZL13gAbortHandler|mozalloc_oom.cpp:void (* gAbortHandler)(size_t)
|
||||
// _ZL14pMutexUnlockFn|umutex.cpp:void (* pMutexUnlockFn)(const void*
|
||||
var name = readable(fullVariable);
|
||||
|
||||
if (name in ignoreIndirectCalls)
|
||||
return true;
|
||||
|
||||
@ -42,8 +50,7 @@ function indirectCallCannotGC(caller, name)
|
||||
return true;
|
||||
|
||||
// template method called during marking and hence cannot GC
|
||||
if (name == "op" &&
|
||||
/^bool js::WeakMap<Key, Value, HashPolicy>::keyNeedsMark\(JSObject\*\)/.test(caller))
|
||||
if (name == "op" && caller.indexOf("bool js::WeakMap<Key, Value, HashPolicy>::keyNeedsMark(JSObject*)") != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -80,6 +87,7 @@ var ignoreCallees = {
|
||||
"mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
|
||||
"nsIThreadManager.GetIsMainThread" : true,
|
||||
"PLDHashTableOps.hashKey" : true,
|
||||
"z_stream_s.zfree" : true,
|
||||
};
|
||||
|
||||
function fieldCallCannotGC(csu, fullfield)
|
||||
@ -91,16 +99,6 @@ function fieldCallCannotGC(csu, fullfield)
|
||||
return false;
|
||||
}
|
||||
|
||||
function shouldSuppressGC(name)
|
||||
{
|
||||
// Various dead code that should only be called inside AutoEnterAnalysis.
|
||||
// Functions with no known caller are by default treated as not suppressing GC.
|
||||
return /TypeScript::Purge/.test(name)
|
||||
|| /StackTypeSet::addPropagateThis/.test(name)
|
||||
|| /ScriptAnalysis::addPushedType/.test(name)
|
||||
|| /IonBuilder/.test(name);
|
||||
}
|
||||
|
||||
function ignoreEdgeUse(edge, variable)
|
||||
{
|
||||
// Functions which should not be treated as using variable.
|
||||
@ -177,8 +175,11 @@ var ignoreFunctions = {
|
||||
"void js::AutoCompartment::AutoCompartment(js::ExclusiveContext*, JSCompartment*)": true,
|
||||
};
|
||||
|
||||
function ignoreGCFunction(fun)
|
||||
function ignoreGCFunction(mangled)
|
||||
{
|
||||
assert(mangled in readableNames);
|
||||
var fun = readableNames[mangled][0];
|
||||
|
||||
if (fun in ignoreFunctions)
|
||||
return true;
|
||||
|
||||
@ -247,5 +248,9 @@ function isOverridableField(csu, field)
|
||||
return false;
|
||||
if (field == 'IsOnCurrentThread')
|
||||
return false;
|
||||
if (field == 'GetNativeContext')
|
||||
return false;
|
||||
if (field == 'GetThreadFromPRThread')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -123,12 +123,7 @@ function getCallees(edge)
|
||||
var callees = [];
|
||||
if (callee.Kind == "Var") {
|
||||
assert(callee.Variable.Kind == "Func");
|
||||
var origName = callee.Variable.Name[0];
|
||||
var names = [ origName, otherDestructorName(origName) ];
|
||||
for (var name of names) {
|
||||
if (name)
|
||||
callees.push({'kind': 'direct', 'name': name});
|
||||
}
|
||||
callees.push({'kind': 'direct', 'name': callee.Variable.Name[0]});
|
||||
} else {
|
||||
assert(callee.Kind == "Drf");
|
||||
if (callee.Exp[0].Kind == "Fld") {
|
||||
|
@ -23,17 +23,21 @@ printErr("Writing " + gcFunctions_filename);
|
||||
redirect(gcFunctions_filename);
|
||||
for (var name in gcFunctions) {
|
||||
print("");
|
||||
print("GC Function: " + name);
|
||||
print("GC Function: " + name + "|" + readableNames[name][0]);
|
||||
do {
|
||||
name = gcFunctions[name];
|
||||
print(" " + name);
|
||||
if (name in readableNames)
|
||||
print(" " + readableNames[name][0]);
|
||||
else
|
||||
print(" " + name);
|
||||
} while (name in gcFunctions);
|
||||
}
|
||||
|
||||
printErr("Writing " + gcFunctionsList_filename);
|
||||
redirect(gcFunctionsList_filename);
|
||||
for (var name in gcFunctions) {
|
||||
print(name);
|
||||
for (var readable of readableNames[name])
|
||||
print(name + "|" + readable);
|
||||
}
|
||||
|
||||
// gcEdges is a list of edges that can GC for more specific reasons than just
|
||||
|
@ -2,11 +2,38 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var calleeGraph = {};
|
||||
var callerGraph = {};
|
||||
var gcFunctions = {};
|
||||
loadRelativeToScript('utility.js');
|
||||
|
||||
// Functions come out of sixgill in the form "mangled|readable". The mangled
|
||||
// name is Truth. One mangled name might correspond to multiple readable names,
|
||||
// for multiple reasons, including (1) sixgill/gcc doesn't always qualify types
|
||||
// the same way or de-typedef the same amount; (2) sixgill's output treats
|
||||
// references and pointers the same, and so doesn't distinguish them, but C++
|
||||
// treats them as separate for overloading and linking; (3) (identical)
|
||||
// destructors sometimes have an int32 parameter, sometimes not.
|
||||
//
|
||||
// The readable names are useful because they're far more meaningful to the
|
||||
// user, and are what should show up in reports and questions to mrgiggles. At
|
||||
// least in most cases, it's fine to have the extra mangled name tacked onto
|
||||
// the beginning for these.
|
||||
//
|
||||
// The strategy used is to separate out the pieces whenever they are read in,
|
||||
// create a table mapping mangled names to (one of the) readable names, and
|
||||
// use the mangled names in all computation.
|
||||
//
|
||||
// Note that callgraph.txt uses a compressed representation -- each name is
|
||||
// mapped to an integer, and those integers are what is recorded in the edges.
|
||||
// But the integers depend on the full name, whereas the true edge should only
|
||||
// consider the mangled name. And some of the names encoded in callgraph.txt
|
||||
// are FieldCalls, not just function names.
|
||||
|
||||
var readableNames = {}; // map from mangled name => list of readable names
|
||||
var mangledName = {}; // map from demangled names => mangled names. Could be eliminated.
|
||||
var calleeGraph = {}; // map from mangled => list of tuples of {'callee':mangled, 'suppressed':bool}
|
||||
var callerGraph = {}; // map from mangled => list of tuples of {'caller':mangled, 'suppressed':bool}
|
||||
var gcFunctions = {}; // map from mangled callee => reason
|
||||
var suppressedFunctions = {}; // set of mangled names (map from mangled name => true)
|
||||
var gcEdges = {};
|
||||
var suppressedFunctions = {};
|
||||
|
||||
function addGCFunction(caller, reason)
|
||||
{
|
||||
@ -35,8 +62,13 @@ function addCallEdge(caller, callee, suppressed)
|
||||
callerGraph[callee].push({caller:caller, suppressed:suppressed});
|
||||
}
|
||||
|
||||
// Map from identifier to full "mangled|readable" name. Or sometimes to a
|
||||
// Class.Field name.
|
||||
var functionNames = [""];
|
||||
|
||||
// Map from identifier to mangled name (or to a Class.Field)
|
||||
var idToMangled = [""];
|
||||
|
||||
function loadCallgraph(file)
|
||||
{
|
||||
var suppressedFieldCalls = {};
|
||||
@ -45,37 +77,45 @@ function loadCallgraph(file)
|
||||
var textLines = snarf(file).split('\n');
|
||||
for (var line of textLines) {
|
||||
var match;
|
||||
if (match = /^\#(\d+) (.*)/.exec(line)) {
|
||||
if (match = line.charAt(0) == "#" && /^\#(\d+) (.*)/.exec(line)) {
|
||||
assert(functionNames.length == match[1]);
|
||||
functionNames.push(match[2]);
|
||||
var [ mangled, readable ] = splitFunction(match[2]);
|
||||
if (mangled in readableNames)
|
||||
readableNames[mangled].push(readable);
|
||||
else
|
||||
readableNames[mangled] = [ readable ];
|
||||
mangledName[readable] = mangled;
|
||||
idToMangled.push(mangled);
|
||||
continue;
|
||||
}
|
||||
var suppressed = false;
|
||||
if (/SUPPRESS_GC/.test(line)) {
|
||||
if (line.indexOf("SUPPRESS_GC") != -1) {
|
||||
match = /^(..)SUPPRESS_GC (.*)/.exec(line);
|
||||
line = match[1] + match[2];
|
||||
suppressed = true;
|
||||
}
|
||||
if (match = /^I (\d+) VARIABLE ([^\,]*)/.exec(line)) {
|
||||
var caller = functionNames[match[1]];
|
||||
var tag = line.charAt(0);
|
||||
if (match = tag == 'I' && /^I (\d+) VARIABLE ([^\,]*)/.exec(line)) {
|
||||
var mangledCaller = idToMangled[match[1]];
|
||||
var name = match[2];
|
||||
if (!indirectCallCannotGC(caller, name) && !suppressed)
|
||||
addGCFunction(caller, "IndirectCall: " + name);
|
||||
} else if (match = /^F (\d+) CLASS (.*?) FIELD (.*)/.exec(line)) {
|
||||
var caller = functionNames[match[1]];
|
||||
if (!indirectCallCannotGC(functionNames[match[1]], name) && !suppressed)
|
||||
addGCFunction(mangledCaller, "IndirectCall: " + name);
|
||||
} else if (match = tag == 'F' && /^F (\d+) CLASS (.*?) FIELD (.*)/.exec(line)) {
|
||||
var caller = idToMangled[match[1]];
|
||||
var csu = match[2];
|
||||
var fullfield = csu + "." + match[3];
|
||||
if (suppressed)
|
||||
suppressedFieldCalls[fullfield] = true;
|
||||
else if (!fieldCallCannotGC(csu, fullfield))
|
||||
addGCFunction(caller, "FieldCall: " + fullfield);
|
||||
} else if (match = /^D (\d+) (\d+)/.exec(line)) {
|
||||
var caller = functionNames[match[1]];
|
||||
var callee = functionNames[match[2]];
|
||||
} else if (match = tag == 'D' && /^D (\d+) (\d+)/.exec(line)) {
|
||||
var caller = idToMangled[match[1]];
|
||||
var callee = idToMangled[match[2]];
|
||||
addCallEdge(caller, callee, suppressed);
|
||||
} else if (match = /^R (\d+) (\d+)/.exec(line)) {
|
||||
var callerField = functionNames[match[1]];
|
||||
var callee = functionNames[match[2]];
|
||||
} else if (match = tag == 'R' && /^R (\d+) (\d+)/.exec(line)) {
|
||||
var callerField = idToMangled[match[1]];
|
||||
var callee = idToMangled[match[2]];
|
||||
addCallEdge(callerField, callee, false);
|
||||
resolvedFunctions[callerField] = true;
|
||||
}
|
||||
@ -99,8 +139,6 @@ function loadCallgraph(file)
|
||||
var top = worklist.length;
|
||||
while (top > 0) {
|
||||
name = worklist[--top];
|
||||
if (shouldSuppressGC(name))
|
||||
continue;
|
||||
if (!(name in suppressedFunctions))
|
||||
continue;
|
||||
delete suppressedFunctions[name];
|
||||
@ -125,8 +163,8 @@ function loadCallgraph(file)
|
||||
for (var gcName of [ 'jsgc.cpp:void Collect(JSRuntime*, uint8, int64, uint32, uint32)',
|
||||
'void js::MinorGC(JSRuntime*, uint32)' ])
|
||||
{
|
||||
assert(gcName in callerGraph);
|
||||
addGCFunction(gcName, "GC");
|
||||
assert(gcName in mangledName);
|
||||
addGCFunction(mangledName[gcName], "GC");
|
||||
}
|
||||
|
||||
// Initialize the worklist to all known gcFunctions.
|
||||
|
@ -233,8 +233,10 @@ sub run_build
|
||||
print CONFIG "$prefix_dir\n";
|
||||
print CONFIG Cwd::abs_path("$result_dir/build_xgill.log")."\n";
|
||||
print CONFIG "$address\n";
|
||||
print CONFIG "-fplugin-arg-xgill-annfile=$ann_file\n"
|
||||
if ($ann_file ne "" && -e $ann_file);
|
||||
my @extra = ("-fplugin-arg-xgill-mangle=1");
|
||||
push(@extra, "-fplugin-arg-xgill-annfile=$ann_file")
|
||||
if ($ann_file ne "" && -e $ann_file);
|
||||
print CONFIG join(" ", @extra) . "\n";
|
||||
close(CONFIG);
|
||||
|
||||
# Tell the wrapper where to find the config
|
||||
|
@ -103,21 +103,30 @@ function getSuccessors(body)
|
||||
return body.successors;
|
||||
}
|
||||
|
||||
function otherDestructorName(name)
|
||||
// Split apart a function from sixgill into its mangled and unmangled name. If
|
||||
// no mangled name was given, use the unmangled name as its mangled name
|
||||
function splitFunction(func)
|
||||
{
|
||||
// gcc's information for destructors can be pretty messed up. Some functions
|
||||
// have destructors with no arguments, some have destructors with an int32
|
||||
// argument, some have both, and which one matches what the programmer wrote
|
||||
// is anyone's guess. Work around this by treating calls to one destructor
|
||||
// form as a call to both destructor forms.
|
||||
if (!/::~/.test(name))
|
||||
return null;
|
||||
var split = func.indexOf("|");
|
||||
if (split == -1)
|
||||
return [ func, func ];
|
||||
return [ func.substr(0, split), func.substr(split+1) ];
|
||||
}
|
||||
|
||||
if (/\(int32\)/.test(name))
|
||||
return name.replace("(int32)","()");
|
||||
if (/\(\)/.test(name))
|
||||
return name.replace("()","(int32)");
|
||||
return null;
|
||||
function mangled(fullname)
|
||||
{
|
||||
var split = fullname.indexOf("|");
|
||||
if (split == -1)
|
||||
return fullname;
|
||||
return fullname.substr(0, split);
|
||||
}
|
||||
|
||||
function readable(fullname)
|
||||
{
|
||||
var split = fullname.indexOf("|");
|
||||
if (split == -1)
|
||||
return fullname;
|
||||
return fullname.substr(split+1);
|
||||
}
|
||||
|
||||
function xdbLibrary()
|
||||
|
Loading…
Reference in New Issue
Block a user