mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1246804 - Switch to using in-source annotations. Use C++ inheritance information when describing GC types. Add a test suite., r=terrence
MozReview-Commit-ID: HCcG2k8Wyb9 --HG-- extra : rebase_source : 732ef6ecc52f0e528d38b8c42e442919eba9b5ae extra : source : 901b1c651c982ccbf42604231c723d168e1cde69
This commit is contained in:
parent
3608515672
commit
517605b022
@ -24,6 +24,7 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "js/GCAnnotations.h"
|
||||
#include "js/Value.h"
|
||||
#include "nscore.h"
|
||||
#include "nsStringGlue.h"
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include "js/GCAnnotations.h"
|
||||
#include "js/HeapAPI.h"
|
||||
#include "js/UniquePtr.h"
|
||||
|
||||
@ -581,7 +582,7 @@ class JS_PUBLIC_API(AutoCheckCannotGC) : public AutoAssertOnGC
|
||||
public:
|
||||
AutoCheckCannotGC() : AutoAssertOnGC() {}
|
||||
explicit AutoCheckCannotGC(JSRuntime* rt) : AutoAssertOnGC(rt) {}
|
||||
};
|
||||
} JS_HAZ_GC_INVALIDATED;
|
||||
|
||||
/**
|
||||
* Unsets the gray bit for anything reachable from |thing|. |kind| should not be
|
||||
|
53
js/public/GCAnnotations.h
Normal file
53
js/public/GCAnnotations.h
Normal file
@ -0,0 +1,53 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef js_GCAnnotations_h
|
||||
#define js_GCAnnotations_h
|
||||
|
||||
// Set of annotations for the rooting hazard analysis, used to categorize types
|
||||
// and functions.
|
||||
#ifdef XGILL_PLUGIN
|
||||
|
||||
// Mark a type as being a GC thing (eg js::gc::Cell has this annotation).
|
||||
# define JS_HAZ_GC_THING __attribute__((tag("GC Thing")))
|
||||
|
||||
// Mark a type as holding a pointer to a GC thing (eg JS::Value has this
|
||||
// annotation.)
|
||||
# define JS_HAZ_GC_POINTER __attribute__((tag("GC Pointer")))
|
||||
|
||||
// Mark a type as a rooted pointer, suitable for use on the stack (eg all
|
||||
// Rooted<T> instantiations should have this.)
|
||||
# define JS_HAZ_ROOTED __attribute__((tag("Rooted Pointer")))
|
||||
|
||||
// Mark a type as something that should not be held live across a GC, but which
|
||||
// is itself not a GC pointer.
|
||||
# define JS_HAZ_GC_INVALIDATED __attribute__((tag("Invalidated by GC")))
|
||||
|
||||
// Mark a type that would otherwise be considered a GC Pointer (eg because it
|
||||
// contains a JS::Value field) as a non-GC pointer. It is handled almost the
|
||||
// same in the analysis as a rooted pointer, except it will not be reported as
|
||||
// an unnecessary root if used across a GC call. This should rarely be used,
|
||||
// but makes sense for something like ErrorResult, which only contains a GC
|
||||
// pointer when it holds an exception (and it does its own rooting,
|
||||
// conditionally.)
|
||||
# define JS_HAZ_NON_GC_POINTER __attribute__((tag("Suppressed GC Pointer")))
|
||||
|
||||
// Mark a function as something that runs a garbage collection, potentially
|
||||
// invalidating GC pointers.
|
||||
# define JS_HAZ_GC_CALL __attribute__((tag("GC Call")))
|
||||
|
||||
#else
|
||||
|
||||
# define JS_HAZ_GC_THING
|
||||
# define JS_HAZ_GC_POINTER
|
||||
# define JS_HAZ_ROOTED
|
||||
# define JS_HAZ_GC_INVALIDATED
|
||||
# define JS_HAZ_NON_GC_POINTER
|
||||
# define JS_HAZ_GC_CALL
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* js_GCAnnotations_h */
|
@ -32,7 +32,7 @@ struct jsid
|
||||
size_t asBits;
|
||||
bool operator==(jsid rhs) const { return asBits == rhs.asBits; }
|
||||
bool operator!=(jsid rhs) const { return asBits != rhs.asBits; }
|
||||
};
|
||||
} JS_HAZ_GC_POINTER;
|
||||
#define JSID_BITS(id) (id.asBits)
|
||||
|
||||
#define JSID_TYPE_STRING 0x0
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
#include "jspubtd.h"
|
||||
|
||||
#include "js/GCAnnotations.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/GCPolicyAPI.h"
|
||||
#include "js/HeapAPI.h"
|
||||
@ -707,7 +708,7 @@ class MOZ_RAII Rooted : public js::RootedBase<T>
|
||||
MaybeWrapped ptr;
|
||||
|
||||
Rooted(const Rooted&) = delete;
|
||||
};
|
||||
} JS_HAZ_ROOTED;
|
||||
|
||||
} /* namespace JS */
|
||||
|
||||
@ -1053,7 +1054,7 @@ class PersistentRooted : public js::PersistentRootedBase<T>,
|
||||
T>::Type;
|
||||
|
||||
MaybeWrapped ptr;
|
||||
};
|
||||
} JS_HAZ_ROOTED;
|
||||
|
||||
class JS_PUBLIC_API(ObjectPtr)
|
||||
{
|
||||
|
@ -1395,7 +1395,7 @@ class Value
|
||||
friend jsval_layout (::JSVAL_TO_IMPL)(Value);
|
||||
friend Value JS_VALUE_CONSTEXPR (::IMPL_TO_JSVAL)(jsval_layout l);
|
||||
friend Value JS_VALUE_CONSTEXPR (JS::UndefinedValue)();
|
||||
};
|
||||
} JS_HAZ_GC_POINTER;
|
||||
|
||||
inline bool
|
||||
IsOptimizedPlaceholderMagicValue(const Value& v)
|
||||
|
@ -4,12 +4,12 @@
|
||||
|
||||
var functionBodies;
|
||||
|
||||
function findAllPoints(blockId)
|
||||
function findAllPoints(bodies, blockId)
|
||||
{
|
||||
var points = [];
|
||||
var body;
|
||||
|
||||
for (var xbody of functionBodies) {
|
||||
for (var xbody of bodies) {
|
||||
if (sameBlockId(xbody.BlockId, blockId)) {
|
||||
assert(!body);
|
||||
body = xbody;
|
||||
@ -22,7 +22,7 @@ function findAllPoints(blockId)
|
||||
for (var edge of body.PEdge) {
|
||||
points.push([body, edge.Index[0]]);
|
||||
if (edge.Kind == "Loop")
|
||||
Array.prototype.push.apply(points, findAllPoints(edge.BlockId));
|
||||
Array.prototype.push.apply(points, findAllPoints(bodies, edge.BlockId));
|
||||
}
|
||||
|
||||
return points;
|
||||
@ -55,7 +55,7 @@ function isMatchingDestructor(constructor, edge)
|
||||
// treat each instance separately, such as when different regions of a function
|
||||
// body were guarded by these constructors and you needed to do something
|
||||
// different with each.)
|
||||
function allRAIIGuardedCallPoints(body, isConstructor)
|
||||
function allRAIIGuardedCallPoints(bodies, body, isConstructor)
|
||||
{
|
||||
if (!("PEdge" in body))
|
||||
return [];
|
||||
@ -77,7 +77,7 @@ function allRAIIGuardedCallPoints(body, isConstructor)
|
||||
if (edge.PEdgeCallInstance.Exp.Kind != "Var")
|
||||
continue;
|
||||
|
||||
Array.prototype.push.apply(points, pointsInRAIIScope(body, edge));
|
||||
Array.prototype.push.apply(points, pointsInRAIIScope(bodies, body, edge));
|
||||
}
|
||||
|
||||
return points;
|
||||
@ -133,7 +133,7 @@ function findMatchingConstructor(destructorEdge, body)
|
||||
debugger;
|
||||
}
|
||||
|
||||
function pointsInRAIIScope(body, constructorEdge) {
|
||||
function pointsInRAIIScope(bodies, body, constructorEdge) {
|
||||
var seen = {};
|
||||
var worklist = [constructorEdge.Index[1]];
|
||||
var points = [];
|
||||
@ -150,7 +150,7 @@ function pointsInRAIIScope(body, constructorEdge) {
|
||||
if (isMatchingDestructor(constructorEdge, nedge))
|
||||
continue;
|
||||
if (nedge.Kind == "Loop")
|
||||
Array.prototype.push.apply(points, findAllPoints(nedge.BlockId));
|
||||
Array.prototype.push.apply(points, findAllPoints(bodies, nedge.BlockId));
|
||||
worklist.push(nedge.Index[1]);
|
||||
}
|
||||
}
|
||||
|
4
js/src/devtools/rootAnalysis/analyze.py
Executable file → Normal file
4
js/src/devtools/rootAnalysis/analyze.py
Executable file → Normal file
@ -200,6 +200,10 @@ parser.add_argument('step', metavar='STEP', type=str, nargs='?',
|
||||
help='run starting from this step')
|
||||
parser.add_argument('--source', metavar='SOURCE', type=str, nargs='?',
|
||||
help='source code to analyze')
|
||||
parser.add_argument('--objdir', metavar='DIR', type=str, nargs='?',
|
||||
help='object directory of compiled files')
|
||||
parser.add_argument('--js', metavar='JSSHELL', type=str, nargs='?',
|
||||
help='full path to ctypes-capable JS shell')
|
||||
parser.add_argument('--upto', metavar='UPTO', type=str, nargs='?',
|
||||
help='last step to execute')
|
||||
parser.add_argument('--jobs', '-j', default=None, metavar='JOBS', type=int,
|
||||
|
@ -694,7 +694,7 @@ function process(name, json) {
|
||||
for (var body of functionBodies)
|
||||
body.suppressed = [];
|
||||
for (var body of functionBodies) {
|
||||
for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
|
||||
for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
|
||||
pbody.suppressed[id] = true;
|
||||
}
|
||||
processBodies(functionName);
|
||||
|
@ -351,57 +351,9 @@ function isOverridableField(initialCSU, csu, field)
|
||||
return true;
|
||||
}
|
||||
|
||||
function listGCTypes() {
|
||||
return [
|
||||
'js::gc::Cell',
|
||||
'JSObject',
|
||||
'JSString',
|
||||
'JSFatInlineString',
|
||||
'JSExternalString',
|
||||
'js::Shape',
|
||||
'js::AccessorShape',
|
||||
'js::BaseShape',
|
||||
'JSScript',
|
||||
'js::ObjectGroup',
|
||||
'js::LazyScript',
|
||||
'js::jit::JitCode',
|
||||
'JS::Symbol',
|
||||
];
|
||||
}
|
||||
|
||||
function listGCPointers() {
|
||||
return [
|
||||
'JS::Value',
|
||||
'jsid',
|
||||
|
||||
'js::TypeSet',
|
||||
'js::TypeSet::ObjectKey',
|
||||
'js::TypeSet::Type',
|
||||
|
||||
// AutoCheckCannotGC should also not be held live across a GC function.
|
||||
'JS::AutoCheckCannotGC',
|
||||
];
|
||||
}
|
||||
|
||||
function listNonGCTypes() {
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
function listNonGCPointers() {
|
||||
return [
|
||||
// Both of these are safe only because jsids are currently only made
|
||||
// from "interned" (pinned) strings. Once that changes, both should be
|
||||
// removed from the list.
|
||||
// Safe only because jsids are currently only made from pinned strings.
|
||||
'NPIdentifier',
|
||||
'XPCNativeMember',
|
||||
];
|
||||
}
|
||||
|
||||
// Flexible mechanism for deciding an arbitrary type is a GCPointer. Its one
|
||||
// use turned out to be unnecessary due to another change, but the mechanism
|
||||
// seems useful for something like /Vector.*Something/.
|
||||
function isGCPointer(typeName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -196,11 +196,49 @@ function printOnce(line)
|
||||
}
|
||||
}
|
||||
|
||||
function processBody(caller, body)
|
||||
// Returns a table mapping function name to lists of [annotation-name,
|
||||
// annotation-value] pairs: { function-name => [ [annotation-name, annotation-value] ] }
|
||||
function getAnnotations(body)
|
||||
{
|
||||
var all_annotations = {};
|
||||
for (var v of (body.DefineVariable || [])) {
|
||||
if (v.Variable.Kind != 'Func')
|
||||
continue;
|
||||
var name = v.Variable.Name[0];
|
||||
var annotations = all_annotations[name] = [];
|
||||
|
||||
for (var ann of (v.Type.Annotation || [])) {
|
||||
annotations.push(ann.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return all_annotations;
|
||||
}
|
||||
|
||||
function getTags(functionName, body) {
|
||||
var tags = new Set();
|
||||
var annotations = getAnnotations(body);
|
||||
print(functionName);
|
||||
print(JSON.stringify(annotations));
|
||||
if (functionName in annotations) {
|
||||
print("crawling through");
|
||||
for (var [ annName, annValue ] of annotations[functionName]) {
|
||||
print(` got ${annName}: ${annValue}`);
|
||||
if (annName == 'Tag')
|
||||
tags.add(annValue);
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
function processBody(functionName, body)
|
||||
{
|
||||
if (!('PEdge' in body))
|
||||
return;
|
||||
|
||||
for (var tag of getTags(functionName, body).values())
|
||||
print("T " + memo(functionName) + " " + tag);
|
||||
|
||||
lastline = null;
|
||||
for (var edge of body.PEdge) {
|
||||
if (edge.Kind != "Call")
|
||||
@ -213,7 +251,7 @@ function processBody(caller, body)
|
||||
}
|
||||
for (var callee of getCallees(edge)) {
|
||||
var prologue = (edgeSuppressed || callee.suppressed) ? "SUPPRESS_GC " : "";
|
||||
prologue += memo(caller) + " ";
|
||||
prologue += memo(functionName) + " ";
|
||||
if (callee.kind == 'direct') {
|
||||
if (!(callee.name in seen)) {
|
||||
seen[callee.name] = true;
|
||||
@ -283,21 +321,18 @@ if (theFunctionNameToFind) {
|
||||
minStream = maxStream = index;
|
||||
}
|
||||
|
||||
for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
|
||||
var name = xdb.read_key(nameIndex);
|
||||
var data = xdb.read_entry(name);
|
||||
functionBodies = JSON.parse(data.readString());
|
||||
function process(functionName, functionBodies)
|
||||
{
|
||||
for (var body of functionBodies)
|
||||
body.suppressed = [];
|
||||
for (var body of functionBodies) {
|
||||
for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
|
||||
for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
|
||||
pbody.suppressed[id] = true;
|
||||
}
|
||||
|
||||
seenCallees = {};
|
||||
seenSuppressedCallees = {};
|
||||
|
||||
var functionName = name.readString();
|
||||
for (var body of functionBodies)
|
||||
processBody(functionName, body);
|
||||
|
||||
@ -364,7 +399,12 @@ for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
|
||||
print("D " + memo(C3) + " " + memo(mangled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
|
||||
var name = xdb.read_key(nameIndex);
|
||||
var data = xdb.read_entry(name);
|
||||
process(name.readString(), JSON.parse(data.readString()));
|
||||
xdb.free_string(name);
|
||||
xdb.free_string(data);
|
||||
}
|
||||
|
@ -5,13 +5,30 @@
|
||||
loadRelativeToScript('utility.js');
|
||||
loadRelativeToScript('annotations.js');
|
||||
|
||||
var annotatedGCPointers = [];
|
||||
var annotations = {
|
||||
'GCPointers': [],
|
||||
'GCThings': [],
|
||||
'NonGCTypes': {}, // unused
|
||||
'NonGCPointers': {},
|
||||
'RootedPointers': {},
|
||||
};
|
||||
|
||||
var structureParents = {}; // Map from field => list of <parent, fieldName>
|
||||
var pointerParents = {}; // Map from field => list of <parent, fieldName>
|
||||
var baseClasses = {}; // Map from struct name => list of base class name strings
|
||||
|
||||
var gcTypes = {}; // map from parent struct => Set of GC typed children
|
||||
var gcPointers = {}; // map from parent struct => Set of GC typed children
|
||||
var gcFields = new Map;
|
||||
|
||||
var rootedPointers = {};
|
||||
|
||||
function processCSU(csu, body)
|
||||
{
|
||||
if (!("DataField" in body))
|
||||
return;
|
||||
for (var field of body.DataField) {
|
||||
for (let { 'Base': base } of (body.CSUBaseClass || []))
|
||||
addBaseClass(csu, base);
|
||||
|
||||
for (let field of (body.DataField || [])) {
|
||||
var type = field.Field.Type;
|
||||
var fieldName = field.Field.Name[0];
|
||||
if (type.Kind == "Pointer") {
|
||||
@ -32,20 +49,44 @@ function processCSU(csu, body)
|
||||
addNestedStructure(csu, type.Name, fieldName);
|
||||
}
|
||||
}
|
||||
if (isGCPointer(csu))
|
||||
annotatedGCPointers.push(csu);
|
||||
|
||||
for (let { 'Name': [ annType, tag ] } of (body.Annotation || [])) {
|
||||
if (annType != 'Tag')
|
||||
continue;
|
||||
|
||||
if (tag == 'GC Pointer')
|
||||
annotations.GCPointers.push(csu);
|
||||
else if (tag == 'Invalidated by GC')
|
||||
annotations.GCPointers.push(csu);
|
||||
else if (tag == 'GC Thing')
|
||||
annotations.GCThings.push(csu);
|
||||
else if (tag == 'Suppressed GC Pointer')
|
||||
annotations.NonGCPointers[csu] = true;
|
||||
else if (tag == 'Rooted Pointer')
|
||||
annotations.RootedPointers[csu] = true;
|
||||
}
|
||||
}
|
||||
|
||||
var structureParents = {}; // Map from field => list of <parent, fieldName>
|
||||
var pointerParents = {}; // Map from field => list of <parent, fieldName>
|
||||
|
||||
// csu.field is of type inner
|
||||
function addNestedStructure(csu, inner, field)
|
||||
{
|
||||
if (!(inner in structureParents))
|
||||
structureParents[inner] = [];
|
||||
|
||||
if (field.match(/^field:\d+$/) && (csu in baseClasses) && (baseClasses[csu].indexOf(inner) != -1))
|
||||
return;
|
||||
|
||||
structureParents[inner].push([ csu, field ]);
|
||||
}
|
||||
|
||||
function addBaseClass(csu, base) {
|
||||
if (!(csu in baseClasses))
|
||||
baseClasses[csu] = [];
|
||||
baseClasses[csu].push(base);
|
||||
var k = baseClasses[csu].length;
|
||||
addNestedStructure(csu, base, `<base-${k}>`);
|
||||
}
|
||||
|
||||
function addNestedPointer(csu, inner, field)
|
||||
{
|
||||
if (!(inner in pointerParents))
|
||||
@ -70,11 +111,12 @@ for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) {
|
||||
xdb.free_string(data);
|
||||
}
|
||||
|
||||
var gcTypes = {}; // map from parent struct => Set of GC typed children
|
||||
var gcPointers = {}; // map from parent struct => Set of GC typed children
|
||||
var nonGCTypes = {}; // set of types that would ordinarily be GC types but we are suppressing
|
||||
var nonGCPointers = {}; // set of types that would ordinarily be GC pointers but we are suppressing
|
||||
var gcFields = new Map;
|
||||
// Now that we have the whole hierarchy set up, add all the types and propagate
|
||||
// info.
|
||||
for (let csu of annotations.GCThings)
|
||||
addGCType(csu);
|
||||
for (let csu of annotations.GCPointers)
|
||||
addGCPointer(csu);
|
||||
|
||||
function stars(n) { return n ? '*' + stars(n-1) : '' };
|
||||
|
||||
@ -122,13 +164,13 @@ function markGCType(typeName, child, why, typePtrLevel, fieldPtrLevel, indent)
|
||||
return;
|
||||
|
||||
if (ptrLevel == 0) {
|
||||
if (typeName in nonGCTypes)
|
||||
if (typeName in annotations.NonGCTypes)
|
||||
return;
|
||||
if (!(typeName in gcTypes))
|
||||
gcTypes[typeName] = new Set();
|
||||
gcTypes[typeName].add(why);
|
||||
} else if (ptrLevel == 1) {
|
||||
if (typeName in nonGCPointers)
|
||||
if (typeName in annotations.NonGCPointers)
|
||||
return;
|
||||
if (!(typeName in gcPointers))
|
||||
gcPointers[typeName] = new Set();
|
||||
@ -165,17 +207,8 @@ function addGCPointer(typeName)
|
||||
markGCType(typeName, '<pointer-annotation>', '(annotation)', 1, 0, "");
|
||||
}
|
||||
|
||||
for (var type of listNonGCTypes())
|
||||
nonGCTypes[type] = true;
|
||||
for (var type of listNonGCPointers())
|
||||
nonGCPointers[type] = true;
|
||||
for (var type of listGCTypes())
|
||||
addGCType(type);
|
||||
for (var type of listGCPointers())
|
||||
addGCPointer(type);
|
||||
|
||||
for (var typeName of annotatedGCPointers)
|
||||
addGCPointer(typeName);
|
||||
//for (var type of listNonGCPointers())
|
||||
// annotations.NonGCPointers[type] = true;
|
||||
|
||||
function explain(csu, indent, seen) {
|
||||
if (!seen)
|
||||
@ -186,25 +219,27 @@ function explain(csu, indent, seen) {
|
||||
var fields = gcFields.get(csu);
|
||||
|
||||
if (fields.has('<annotation>')) {
|
||||
print(indent + "which is a GCThing because I said so");
|
||||
print(indent + "which is annotated as a GCThing");
|
||||
return;
|
||||
}
|
||||
if (fields.has('<pointer-annotation>')) {
|
||||
print(indent + "which is a GCPointer because I said so");
|
||||
print(indent + "which is annotated as a GCPointer");
|
||||
return;
|
||||
}
|
||||
for (var [ field, [ child, ptrdness ] ] of fields) {
|
||||
var inherit = "";
|
||||
if (field == "field:0")
|
||||
inherit = " (probably via inheritance)";
|
||||
var msg = indent + "contains field '" + field + "' ";
|
||||
if (ptrdness == -1)
|
||||
msg += "(with a pointer to unsafe storage) holding a ";
|
||||
else if (ptrdness == 0)
|
||||
msg += "of type ";
|
||||
else
|
||||
msg += "pointing to type ";
|
||||
msg += child + inherit;
|
||||
var msg = indent;
|
||||
if (field[0] == '<')
|
||||
msg += "inherits from ";
|
||||
else {
|
||||
msg += "contains field '" + field + "' ";
|
||||
if (ptrdness == -1)
|
||||
msg += "(with a pointer to unsafe storage) holding a ";
|
||||
else if (ptrdness == 0)
|
||||
msg += "of type ";
|
||||
else
|
||||
msg += "pointing to type ";
|
||||
}
|
||||
msg += child;
|
||||
print(msg);
|
||||
if (!seen.has(child))
|
||||
explain(child, indent + " ", seen);
|
||||
|
@ -74,6 +74,8 @@ function loadCallgraph(file)
|
||||
var suppressedFieldCalls = {};
|
||||
var resolvedFunctions = {};
|
||||
|
||||
var numGCCalls = 0;
|
||||
|
||||
for (var line of readFileLines_gen(file)) {
|
||||
line = line.replace(/\n/, "");
|
||||
|
||||
@ -119,6 +121,13 @@ function loadCallgraph(file)
|
||||
var callee = idToMangled[match[2]];
|
||||
addCallEdge(callerField, callee, false);
|
||||
resolvedFunctions[callerField] = true;
|
||||
} else if (match = tag == 'T' && /^T (\d+) (.*)/.exec(line)) {
|
||||
var mangled = idToMangled[match[1]];
|
||||
var tag = match[2];
|
||||
if (tag == 'GC Call') {
|
||||
addGCFunction(mangled, "GC");
|
||||
numGCCalls++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,13 +170,11 @@ function loadCallgraph(file)
|
||||
suppressedFunctions[name] = true;
|
||||
}
|
||||
|
||||
for (var gcName of [ 'void js::gc::GCRuntime::collect(uint8, js::SliceBudget, uint32)',
|
||||
'void js::gc::GCRuntime::minorGC(uint32)',
|
||||
'void js::gc::GCRuntime::minorGC(uint32)' ])
|
||||
{
|
||||
assert(gcName in mangledName, "GC function not found: " + gcName);
|
||||
addGCFunction(mangledName[gcName], "GC");
|
||||
}
|
||||
// Sanity check to make sure the callgraph has some functions annotated as
|
||||
// GC Calls. This is mostly a check to be sure the earlier processing
|
||||
// succeeded (as opposed to, say, running on empty xdb files because you
|
||||
// didn't actually compile anything interesting.)
|
||||
assert(numGCCalls > 0, "No GC functions found!");
|
||||
|
||||
// Initialize the worklist to all known gcFunctions.
|
||||
var worklist = [];
|
||||
|
147
js/src/devtools/rootAnalysis/run-test.py
Normal file
147
js/src/devtools/rootAnalysis/run-test.py
Normal file
@ -0,0 +1,147 @@
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
testdir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
cfg = {}
|
||||
cfg['SIXGILL_ROOT'] = os.environ.get('SIXGILL',
|
||||
os.path.join(testdir, "sixgill"))
|
||||
cfg['SIXGILL_BIN'] = os.environ.get('SIXGILL_BIN',
|
||||
os.path.join(cfg['SIXGILL_ROOT'], "usr", "bin"))
|
||||
cfg['SIXGILL_PLUGIN'] = os.environ.get('SIXGILL_PLUGIN',
|
||||
os.path.join(cfg['SIXGILL_ROOT'], "usr", "libexec", "sixgill", "gcc", "xgill.so"))
|
||||
cfg['CC'] = os.environ.get("CC",
|
||||
"gcc")
|
||||
cfg['CXX'] = os.environ.get("CXX",
|
||||
cfg.get('CC', 'g++'))
|
||||
cfg['JS_BIN'] = os.environ["JS"]
|
||||
|
||||
def binpath(prog):
|
||||
return os.path.join(cfg['SIXGILL_BIN'], prog)
|
||||
|
||||
if not os.path.exists("test-output"):
|
||||
os.mkdir("test-output")
|
||||
|
||||
# Simplified version of the body info.
|
||||
class Body(dict):
|
||||
def __init__(self, body):
|
||||
self['BlockIdKind'] = body['BlockId']['Kind']
|
||||
if 'Variable' in body['BlockId']:
|
||||
self['BlockName'] = body['BlockId']['Variable']['Name'][0]
|
||||
self['LineRange'] = [ body['Location'][0]['Line'], body['Location'][1]['Line'] ]
|
||||
self['Filename'] = body['Location'][0]['CacheString']
|
||||
self['Edges'] = body.get('PEdge', [])
|
||||
self['Points'] = { i+1: body['PPoint'][i]['Location']['Line'] for i in range(len(body['PPoint'])) }
|
||||
self['Index'] = body['Index']
|
||||
self['Variables'] = { x['Variable']['Name'][0]: x['Type'] for x in body['DefineVariable'] }
|
||||
|
||||
# Indexes
|
||||
self['Line2Points'] = {}
|
||||
for point, line in self['Points'].items():
|
||||
self['Line2Points'].setdefault(line, []).append(point)
|
||||
self['SrcPoint2Edges'] = {}
|
||||
for edge in self['Edges']:
|
||||
(src, dst) = edge['Index']
|
||||
self['SrcPoint2Edges'].setdefault(src, []).append(edge)
|
||||
self['Line2Edges'] = {}
|
||||
for (src, edges) in self['SrcPoint2Edges'].items():
|
||||
line = self['Points'][src]
|
||||
self['Line2Edges'].setdefault(line, []).extend(edges)
|
||||
|
||||
def edges_from_line(self, line):
|
||||
return self['Line2Edges'][line]
|
||||
|
||||
def edge_from_line(self, line):
|
||||
edges = self.edges_from_line(line)
|
||||
assert(len(edges) == 1)
|
||||
return edges[0]
|
||||
|
||||
def edges_from_point(self, point):
|
||||
return self['SrcPoint2Edges'][point]
|
||||
|
||||
def edge_from_point(self, point):
|
||||
edges = self.edges_from_point(point)
|
||||
assert(len(edges) == 1)
|
||||
return edges[0]
|
||||
|
||||
def assignment_point(self, varname):
|
||||
for edge in self['Edges']:
|
||||
if edge['Kind'] != 'Assign':
|
||||
continue
|
||||
dst = edge['Exp'][0]
|
||||
if dst['Kind'] != 'Var':
|
||||
continue
|
||||
if dst['Variable']['Name'][0] == varname:
|
||||
return edge['Index'][0]
|
||||
raise Exception("assignment to variable %s not found" % varname)
|
||||
|
||||
def assignment_line(self, varname):
|
||||
return self['Points'][self.assignment_point(varname)]
|
||||
|
||||
tests = ['test']
|
||||
for name in tests:
|
||||
indir = os.path.join(testdir, name)
|
||||
outdir = os.path.join(testdir, "test-output", name)
|
||||
if not os.path.exists(outdir):
|
||||
os.mkdir(outdir)
|
||||
|
||||
def compile(source):
|
||||
cmd = "{CXX} -c {source} -fplugin={sixgill}".format(source=os.path.join(indir, source),
|
||||
CXX=cfg['CXX'], sixgill=cfg['SIXGILL_PLUGIN'])
|
||||
print("Running %s" % cmd)
|
||||
subprocess.check_call(["sh", "-c", cmd])
|
||||
|
||||
def load_db_entry(dbname, pattern):
|
||||
if not isinstance(pattern, basestring):
|
||||
output = subprocess.check_output([binpath("xdbkeys"), dbname + ".xdb"])
|
||||
entries = output.splitlines()
|
||||
matches = [f for f in entries if re.search(pattern, f)]
|
||||
if len(matches) == 0:
|
||||
raise Exception("entry not found")
|
||||
if len(matches) > 1:
|
||||
raise Exception("multiple entries found")
|
||||
pattern = matches[0]
|
||||
|
||||
output = subprocess.check_output([binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
|
||||
return json.loads(output)
|
||||
|
||||
def computeGCTypes():
|
||||
file("defaults.py", "w").write('''\
|
||||
analysis_scriptdir = '{testdir}'
|
||||
sixgill_bin = '{bindir}'
|
||||
'''.format(testdir=testdir, bindir=cfg['SIXGILL_BIN']))
|
||||
cmd = [
|
||||
os.path.join(testdir, "analyze.py"),
|
||||
"gcTypes", "--upto", "gcTypes",
|
||||
"--source=%s" % indir,
|
||||
"--objdir=%s" % outdir,
|
||||
"--js=%s" % cfg['JS_BIN'],
|
||||
]
|
||||
print("Running " + " ".join(cmd))
|
||||
output = subprocess.check_call(cmd)
|
||||
|
||||
def loadGCTypes():
|
||||
gctypes = {'GCThings': [], 'GCPointers': []}
|
||||
for line in file(os.path.join(outdir, "gcTypes.txt")):
|
||||
m = re.match(r'^(GC\w+): (.*)', line)
|
||||
if m:
|
||||
gctypes[m.group(1) + 's'].append(m.group(2))
|
||||
return gctypes
|
||||
|
||||
def process_body(body):
|
||||
return Body(body)
|
||||
|
||||
def process_bodies(bodies):
|
||||
return [ process_body(b) for b in bodies ]
|
||||
|
||||
def equal(got, expected):
|
||||
if got != expected:
|
||||
print("Got '%s', expected '%s'" % (got, expected))
|
||||
|
||||
os.chdir(outdir)
|
||||
subprocess.call(["sh", "-c", "rm *.xdb"])
|
||||
execfile(os.path.join(indir, "test.py"))
|
||||
print("TEST-PASSED: %s" % name)
|
70
js/src/devtools/rootAnalysis/test/source.cpp
Normal file
70
js/src/devtools/rootAnalysis/test/source.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#define ANNOTATE(property) __attribute__((tag(property)))
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
struct Cell { int f; } ANNOTATE("GC Thing");
|
||||
}
|
||||
}
|
||||
|
||||
struct Bogon {
|
||||
};
|
||||
|
||||
struct JustACell : public js::gc::Cell {
|
||||
bool iHaveNoDataMembers() { return true; }
|
||||
};
|
||||
|
||||
struct JSObject : public js::gc::Cell, public Bogon {
|
||||
int g;
|
||||
};
|
||||
|
||||
struct SpecialObject : public JSObject {
|
||||
int z;
|
||||
};
|
||||
|
||||
struct ErrorResult {
|
||||
bool hasObj;
|
||||
JSObject *obj;
|
||||
void trace() {}
|
||||
} ANNOTATE("Suppressed GC Pointer");
|
||||
|
||||
struct OkContainer {
|
||||
ErrorResult res;
|
||||
bool happy;
|
||||
};
|
||||
|
||||
struct UnrootedPointer {
|
||||
JSObject *obj;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Rooted {
|
||||
T data;
|
||||
} ANNOTATE("Rooted Pointer");
|
||||
|
||||
extern void js_GC() ANNOTATE("GC Call") ANNOTATE("Slow");
|
||||
|
||||
void js_GC() {}
|
||||
|
||||
void root_arg(JSObject *obj, JSObject *random)
|
||||
{
|
||||
// Use all these types so they get included in the output.
|
||||
SpecialObject so;
|
||||
UnrootedPointer up;
|
||||
Bogon b;
|
||||
OkContainer okc;
|
||||
Rooted<JSObject*> ro;
|
||||
Rooted<SpecialObject*> rso;
|
||||
|
||||
obj = random;
|
||||
|
||||
JSObject *other1 = obj;
|
||||
js_GC();
|
||||
|
||||
float MARKER1 = 0;
|
||||
JSObject *other2 = obj;
|
||||
other1->f = 1;
|
||||
other2->f = -1;
|
||||
|
||||
unsigned int u1 = 1;
|
||||
unsigned int u2 = -1;
|
||||
}
|
58
js/src/devtools/rootAnalysis/test/test.py
Normal file
58
js/src/devtools/rootAnalysis/test/test.py
Normal file
@ -0,0 +1,58 @@
|
||||
compile("source.cpp")
|
||||
computeGCTypes()
|
||||
body = process_body(load_db_entry("src_body", re.compile(r'root_arg'))[0])
|
||||
|
||||
# Rendering positive and negative integers
|
||||
marker1 = body.assignment_line('MARKER1')
|
||||
equal(body.edge_from_line(marker1 + 2)['Exp'][1]['String'], '1')
|
||||
equal(body.edge_from_line(marker1 + 3)['Exp'][1]['String'], '-1')
|
||||
|
||||
equal(body.edge_from_point(body.assignment_point('u1'))['Exp'][1]['String'], '1')
|
||||
equal(body.edge_from_point(body.assignment_point('u2'))['Exp'][1]['String'], '4294967295')
|
||||
|
||||
assert('obj' in body['Variables'])
|
||||
assert('random' in body['Variables'])
|
||||
assert('other1' in body['Variables'])
|
||||
assert('other2' in body['Variables'])
|
||||
|
||||
# Test function annotations
|
||||
js_GC = process_body(load_db_entry("src_body", re.compile(r'js_GC'))[0])
|
||||
annotations = js_GC['Variables']['void js_GC()']['Annotation']
|
||||
assert(annotations)
|
||||
found_call_tag = False
|
||||
for annotation in annotations:
|
||||
(annType, value) = annotation['Name']
|
||||
if annType == 'Tag' and value == 'GC Call':
|
||||
found_call_tag = True
|
||||
assert(found_call_tag)
|
||||
|
||||
# Test type annotations
|
||||
|
||||
# js::gc::Cell first
|
||||
cell = load_db_entry("src_comp", 'js::gc::Cell')[0]
|
||||
assert(cell['Kind'] == 'Struct')
|
||||
annotations = cell['Annotation']
|
||||
assert(len(annotations) == 1)
|
||||
(tag, value) = annotations[0]['Name']
|
||||
assert(tag == 'Tag')
|
||||
assert(value == 'GC Thing')
|
||||
|
||||
# Check JSObject inheritance.
|
||||
JSObject = load_db_entry("src_comp", 'JSObject')[0]
|
||||
bases = [ b['Base'] for b in JSObject['CSUBaseClass'] ]
|
||||
assert('js::gc::Cell' in bases)
|
||||
assert('Bogon' in bases)
|
||||
assert(len(bases) == 2)
|
||||
|
||||
# Check type analysis
|
||||
gctypes = loadGCTypes()
|
||||
assert('js::gc::Cell' in gctypes['GCThings'])
|
||||
assert('JustACell' in gctypes['GCThings'])
|
||||
assert('JSObject' in gctypes['GCThings'])
|
||||
assert('SpecialObject' in gctypes['GCThings'])
|
||||
assert('UnrootedPointer' in gctypes['GCPointers'])
|
||||
assert('Bogon' not in gctypes['GCThings'])
|
||||
assert('Bogon' not in gctypes['GCPointers'])
|
||||
assert('ErrorResult' not in gctypes['GCPointers'])
|
||||
assert('OkContainer' not in gctypes['GCPointers'])
|
||||
assert('class Rooted<JSObject*>' not in gctypes['GCPointers'])
|
@ -17,6 +17,7 @@
|
||||
#include "gc/Statistics.h"
|
||||
#include "gc/StoreBuffer.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "js/GCAnnotations.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
@ -870,7 +871,7 @@ class GCRuntime
|
||||
Finished
|
||||
};
|
||||
|
||||
void minorGCImpl(JS::gcreason::Reason reason, Nursery::ObjectGroupList* pretenureGroups);
|
||||
void minorGCImpl(JS::gcreason::Reason reason, Nursery::ObjectGroupList* pretenureGroups) JS_HAZ_GC_CALL;
|
||||
|
||||
// For ArenaLists::allocateFromArena()
|
||||
friend class ArenaLists;
|
||||
@ -916,7 +917,7 @@ class GCRuntime
|
||||
bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason);
|
||||
|
||||
gcstats::ZoneGCStats scanZonesBeforeGC();
|
||||
void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason);
|
||||
void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason) JS_HAZ_GC_CALL;
|
||||
bool gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason);
|
||||
void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason);
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "gc/Memory.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/HeapAPI.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/TracingAPI.h"
|
||||
|
||||
struct JSRuntime;
|
||||
@ -247,7 +248,7 @@ struct Cell
|
||||
protected:
|
||||
inline uintptr_t address() const;
|
||||
inline Chunk* chunk() const;
|
||||
};
|
||||
} JS_HAZ_GC_THING;
|
||||
|
||||
// A GC TenuredCell gets behaviors that are valid for things in the Tenured
|
||||
// heap, such as access to the arena and mark bits.
|
||||
|
@ -33,7 +33,7 @@ with Files('jit/**'):
|
||||
for gcfile in ['jsgc*', 'devtools/rootAnalysis', 'devtools/gc-ubench', 'devtools/gctrace']:
|
||||
with Files(gcfile):
|
||||
BUG_COMPONENT = component_gc
|
||||
for header in ('GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
|
||||
for header in ('GCAnnotations.h', 'GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
|
||||
with Files('../public/' + header):
|
||||
BUG_COMPONENT = component_gc
|
||||
|
||||
@ -108,6 +108,7 @@ EXPORTS.js += [
|
||||
'../public/Conversions.h',
|
||||
'../public/Date.h',
|
||||
'../public/Debug.h',
|
||||
'../public/GCAnnotations.h',
|
||||
'../public/GCAPI.h',
|
||||
'../public/GCHashTable.h',
|
||||
'../public/GCPolicyAPI.h',
|
||||
|
@ -267,7 +267,7 @@ class TypeSet
|
||||
void ensureTrackedProperty(JSContext* cx, jsid id);
|
||||
|
||||
ObjectGroup* maybeGroup();
|
||||
};
|
||||
} JS_HAZ_GC_POINTER;
|
||||
|
||||
// Information about a single concrete type. We pack this into one word,
|
||||
// where small values are particular primitive or other singleton types and
|
||||
@ -356,7 +356,7 @@ class TypeSet
|
||||
|
||||
bool operator == (Type o) const { return data == o.data; }
|
||||
bool operator != (Type o) const { return data != o.data; }
|
||||
};
|
||||
} JS_HAZ_GC_POINTER;
|
||||
|
||||
static inline Type UndefinedType() { return Type(JSVAL_TYPE_UNDEFINED); }
|
||||
static inline Type NullType() { return Type(JSVAL_TYPE_NULL); }
|
||||
@ -535,7 +535,7 @@ class TypeSet
|
||||
static bool IsTypeMarked(Type* v);
|
||||
static bool IsTypeAllocatedDuringIncremental(Type v);
|
||||
static bool IsTypeAboutToBeFinalized(Type* v);
|
||||
};
|
||||
} JS_HAZ_GC_POINTER;
|
||||
|
||||
/*
|
||||
* A constraint which listens to additions to a type set and propagates those
|
||||
|
@ -1344,7 +1344,7 @@ private:
|
||||
// creation of XPCNativeInterfaces which have more than 2^12 members.
|
||||
// If the width of this field changes, update GetMaxIndexInInterface.
|
||||
uint16_t mIndexInInterface : 12;
|
||||
};
|
||||
} JS_HAZ_NON_GC_POINTER; // Only stores a pinned string
|
||||
|
||||
/***************************************************************************/
|
||||
// XPCNativeInterface represents a single idl declared interface. This is
|
||||
|
2
testing/mozharness/scripts/spidermonkey/build.shell
Executable file → Normal file
2
testing/mozharness/scripts/spidermonkey/build.shell
Executable file → Normal file
@ -5,5 +5,5 @@ set -x
|
||||
|
||||
[ -d $ANALYZED_OBJDIR ] || mkdir $ANALYZED_OBJDIR
|
||||
cd $ANALYZED_OBJDIR
|
||||
$SOURCE/js/src/configure --enable-debug --enable-optimize --enable-stdcxx-compat --enable-ctypes --enable-exact-rooting --enable-gcgenerational --with-system-nspr
|
||||
$SOURCE/js/src/configure --enable-debug --enable-optimize --enable-stdcxx-compat --enable-ctypes --with-system-nspr
|
||||
make -j12 -s
|
||||
|
Loading…
Reference in New Issue
Block a user