author | Steve Fink <sfink@mozilla.com> |
Mon, 16 Jul 2018 17:04:01 -0700 | |
changeset 441534 | f6173c4ae224f1a7df7a23195003fe312331e85d |
parent 441533 | 77ada590e04777dd99d3632cdcd2ed34ea5d4268 |
child 441535 | c45f9de01d9e2d65c740f7386b5bf1f76e8bcf64 |
push id | 34867 |
push user | shindli@mozilla.com |
push date | Wed, 17 Oct 2018 00:55:53 +0000 |
treeherder | mozilla-central@778427bb6353 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jonco |
bugs | 1479868 |
milestone | 64.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/devtools/rootAnalysis/run-test.py +++ b/js/src/devtools/rootAnalysis/run-test.py @@ -38,17 +38,22 @@ parser.add_argument( help='Path to gcc') parser.add_argument( '--cxx', default=os.environ.get('CXX'), help='Path to g++') parser.add_argument( '--verbose', '-v', action='store_true', help='Display verbose output, including commands executed') parser.add_argument( - 'tests', nargs='*', default=['sixgill-tree', 'suppression', 'hazards', 'exceptions'], + 'tests', nargs='*', default=[ + 'sixgill-tree', + 'suppression', + 'hazards', + 'exceptions', + 'virtual'], help='tests to run') cfg = parser.parse_args() if not cfg.js: exit('Must specify JS binary through environment variable or --js option') if not cfg.cc: if cfg.gccdir:
--- a/js/src/devtools/rootAnalysis/t/testlib.py +++ b/js/src/devtools/rootAnalysis/t/testlib.py @@ -3,18 +3,31 @@ import os import re import subprocess from sixgill import Body from collections import defaultdict, namedtuple scriptdir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -HazardSummary = namedtuple( - 'HazardSummary', ['function', 'variable', 'type', 'GCFunction', 'location']) +HazardSummary = namedtuple('HazardSummary', [ + 'function', + 'variable', + 'type', + 'GCFunction', + 'location']) + +Callgraph = namedtuple('Callgraph', [ + 'functionNames', + 'nameToId', + 'calleesOf', + 'callersOf', + 'tags', + 'calleeGraph', + 'callerGraph']) def equal(got, expected): if got != expected: print("Got '%s', expected '%s'" % (got, expected)) def extract_unmangled(func): @@ -85,37 +98,89 @@ sixgill_bin = '{bindir}' self.run_analysis_script("gcTypes") def load_text_file(self, filename, extract=lambda l: l): fullpath = os.path.join(self.outdir, filename) values = (extract(line.strip()) for line in open(fullpath, "r")) return list(filter(lambda _: _ is not None, values)) def load_suppressed_functions(self): - return set(self.load_text_file("suppressedFunctions.lst")) + return set(self.load_text_file("limitedFunctions.lst", extract=lambda l: l.split(' ')[1])) def load_gcTypes(self): def grab_type(line): m = re.match(r'^(GC\w+): (.*)', line) if m: return (m.group(1) + 's', m.group(2)) return None gctypes = defaultdict(list) for collection, typename in self.load_text_file('gcTypes.txt', extract=grab_type): gctypes[collection].append(typename) return gctypes def load_gcFunctions(self): return self.load_text_file('gcFunctions.lst', extract=extract_unmangled) + def load_callgraph(self): + data = Callgraph( + functionNames=['dummy'], + nameToId={}, + calleesOf=defaultdict(list), + callersOf=defaultdict(list), + tags=defaultdict(set), + calleeGraph=defaultdict(dict), + callerGraph=defaultdict(dict), + ) + + def lookup(id): + return data.functionNames[int(id)] + + def add_call(caller, callee, limit): + data.calleesOf[caller].append(callee) + data.callersOf[callee].append(caller) + data.calleeGraph[caller][callee] = True + data.callerGraph[callee][caller] = True + + def process(line): + if line.startswith('#'): + name = line.split(" ", 1)[1] + if '$' in name: + name = name[name.index('$') + 1:] + data.nameToId[name] = len(data.functionNames) + data.functionNames.append(name) + return + + limit = 0 + m = re.match(r'^\w (?:/(\d+))? ', line) + if m: + limit = int(m[1]) + + tokens = line.split(' ') + if tokens[0] in ('D', 'R'): + _, caller, callee = tokens + add_call(lookup(caller), lookup(callee), limit) + elif tokens[0] == 'T': + data.tags[tokens[1]].add(line.split(' ', 2)[2]) + elif tokens[0] in ('F', 'V'): + m = re.match(r'^[FV] (\d+) (\d+) CLASS (.*?) FIELD (.*)', line) + caller, callee, csu, field = m.groups() + add_call(lookup(caller), lookup(callee), limit) + + elif tokens[0] == 'I': + m = re.match(r'^I (\d+) VARIABLE ([^\,]*)', line) + pass + + self.load_text_file('callgraph.txt', extract=process) + return data + def load_hazards(self): def grab_hazard(line): m = re.match( - r"Function '(.*?)' has unrooted '(.*?)' of type '(.*?)' live across GC call '(.*?)' at (.*)", line) # NOQA: E501 + r"Function '(.*?)' has unrooted '(.*?)' of type '(.*?)' live across GC call '?(.*?)'? at (.*)", line) # NOQA: E501 if m: info = list(m.groups()) info[0] = info[0].split("$")[-1] info[3] = info[3].split("$")[-1] return HazardSummary(*info) return None return self.load_text_file('rootingHazards.txt', extract=grab_hazard)
new file mode 100644 --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/virtual/source.cpp @@ -0,0 +1,84 @@ +#define ANNOTATE(property) __attribute__((tag(property))) + +extern void GC() ANNOTATE("GC Call"); + +void GC() +{ + // If the implementation is too trivial, the function body won't be emitted at all. + asm(""); +} + +struct Cell { int f; } ANNOTATE("GC Thing"); + +extern void foo(); + +typedef void (*func_t)(); + +class Base { + public: + int dummy; + virtual void someGC() = 0; + func_t functionField; +}; + +class Super : public Base { + public: + virtual void noneGC() = 0; + virtual void allGC() = 0; +}; + +void bar() { + GC(); +} + +class Sub1 : public Super { + public: + void noneGC() override { foo(); } + void someGC() override { foo(); } + void allGC() override { foo(); bar(); } +}; + +class Sub2 : public Super { + public: + void noneGC() override { foo(); } + void someGC() override { foo(); bar(); } + void allGC() override { foo(); bar(); } +}; + +class Sibling : public Base { + public: + virtual void noneGC() { foo(); } + void someGC() override { foo(); bar(); } + virtual void allGC() { foo(); bar(); } +}; + +class AutoSuppressGC { + public: + AutoSuppressGC() {} + ~AutoSuppressGC() {} +} ANNOTATE("Suppress GC"); + +void use(Cell*) { + asm(""); +} + +void f() { + Sub1 s1; + Sub2 s2; + + Cell cell; + { Cell* c1 = &cell; s1.noneGC(); use(c1); } + { Cell* c2 = &cell; s2.someGC(); use(c2); } + { Cell* c3 = &cell; s1.allGC(); use(c3); } + { Cell* c4 = &cell; s2.noneGC(); use(c4); } + { Cell* c5 = &cell; s2.someGC(); use(c5); } + { Cell* c6 = &cell; s2.allGC(); use(c6); } + + Super* super = &s2; + { Cell* c7 = &cell; super->noneGC(); use(c7); } + { Cell* c8 = &cell; super->someGC(); use(c8); } + { Cell* c9 = &cell; super->allGC(); use(c9); } + + { Cell* c10 = &cell; s1.functionField(); use(c10); } + { Cell* c11 = &cell; super->functionField(); use(c11); } +}
new file mode 100644 --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/virtual/test.py @@ -0,0 +1,38 @@ +test.compile("source.cpp") +test.run_analysis_script('gcTypes') + +# The suppressions file uses only mangled names since it's for internal use, +# though I may change that soon given (1) the unfortunate non-uniqueness of +# mangled constructor names, and (2) the usefulness of this file for +# mrgiggles's reporting. +suppressed = test.load_suppressed_functions() + +# gcFunctions should be the inverse, but we get to rely on unmangled names here. +gcFunctions = test.load_gcFunctions() + +assert 'void Sub1::noneGC()' not in gcFunctions +assert 'void Sub1::someGC()' not in gcFunctions +assert 'void Sub1::allGC()' in gcFunctions +assert 'void Sub2::noneGC()' not in gcFunctions +assert 'void Sub2::someGC()' in gcFunctions +assert 'void Sub2::allGC()' in gcFunctions + +callgraph = test.load_callgraph() +assert callgraph.calleeGraph['void f()']['Super.noneGC'] +assert callgraph.calleeGraph['Super.noneGC']['void Sub1::noneGC()'] +assert callgraph.calleeGraph['Super.noneGC']['void Sub2::noneGC()'] +assert 'void Sibling::noneGC()' not in callgraph.calleeGraph['Super.noneGC'] + +hazards = test.load_hazards() +hazmap = {haz.variable: haz for haz in hazards} +assert 'c1' not in hazmap +assert 'c2' in hazmap +assert 'c3' in hazmap +assert 'c4' not in hazmap +assert 'c5' in hazmap +assert 'c6' in hazmap +assert 'c7' not in hazmap +assert 'c8' in hazmap +assert 'c9' in hazmap +assert 'c10' in hazmap +assert 'c11' in hazmap