Bug 910947 - More static rooting analysis fixes to get it all running on build slaves. r=divine-right
authorSteve Fink <sfink@mozilla.com>
Thu, 29 Aug 2013 14:51:19 -0700
changeset 145022 7c79b89c492a161b2f6f7e643c29e30fb4b4fa96
parent 145021 3f307f37b77a321b9deeb4f26e20d78494ea38d0
child 145023 00e7c35894e2d4d27578955e9756afda33cb72cb
push id33129
push usersfink@mozilla.com
push dateThu, 29 Aug 2013 23:44:27 +0000
treeherdermozilla-inbound@7c79b89c492a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdivine-right
bugs910947
milestone26.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
Bug 910947 - More static rooting analysis fixes to get it all running on build slaves. r=divine-right DONTBUILD 'cause NPOTB
js/src/devtools/rootAnalysis/CFG.js
js/src/devtools/rootAnalysis/analyze.py
js/src/devtools/rootAnalysis/analyzeRoots.js
js/src/devtools/rootAnalysis/annotations.js
js/src/devtools/rootAnalysis/computeCallgraph.js
js/src/devtools/rootAnalysis/run_complete
js/src/devtools/rootAnalysis/suppressedPoints.js
rename from js/src/devtools/rootAnalysis/suppressedPoints.js
rename to js/src/devtools/rootAnalysis/CFG.js
--- a/js/src/devtools/rootAnalysis/suppressedPoints.js
+++ b/js/src/devtools/rootAnalysis/CFG.js
@@ -45,41 +45,110 @@ function isMatchingDestructor(constructo
 
     var destructExp = edge.PEdgeCallInstance.Exp;
     if (destructExp.Kind != "Var")
         return false;
 
     return sameVariable(constructExp.Variable, destructExp.Variable);
 }
 
+// Return all calls within the RAII scope of the constructor matched by
+// isConstructor()
 function allRAIIGuardedCallPoints(body, isConstructor)
 {
     if (!("PEdge" in body))
         return [];
 
     var points = [];
-    var successors = getSuccessors(body);
 
     for (var edge of body.PEdge) {
         if (edge.Kind != "Call")
             continue;
         var callee = edge.Exp[0];
         if (callee.Kind != "Var")
             continue;
         var variable = callee.Variable;
         assert(variable.Kind == "Func");
         if (!isConstructor(variable.Name[0]))
             continue;
         if (edge.PEdgeCallInstance.Exp.Kind != "Var")
             continue;
 
-        Array.prototype.push.apply(points, pointsInRAIIScope(body, edge, successors));
+        Array.prototype.push.apply(points, pointsInRAIIScope(body, edge));
     }
 
     return points;
 }
 
-// Compute the points within a function body where GC is suppressed.
-function computeSuppressedPoints(body)
+// Test whether the given edge is the constructor corresponding to the given
+// destructor edge
+function isMatchingConstructor(destructor, edge)
+{
+    if (edge.Kind != "Call")
+        return false;
+    var callee = edge.Exp[0];
+    if (callee.Kind != "Var")
+        return false;
+    var variable = callee.Variable;
+    if (variable.Kind != "Func")
+        return false;
+    var name = variable.Name[0];
+    var destructorName = destructor.Exp[0].Variable.Name[0];
+    var match = destructorName.match(/^(.*?::)~(\w+)\(/);
+    if (!match) {
+        printErr("Unhandled destructor syntax: " + destructorName);
+        return false;
+    }
+    var constructorSubstring = match[1] + match[2];
+    if (name.indexOf(constructorSubstring) == -1)
+        return false;
+
+    var destructExp = destructor.PEdgeCallInstance.Exp;
+    assert(destructExp.Kind == "Var");
+
+    var constructExp = edge.PEdgeCallInstance.Exp;
+    if (constructExp.Kind != "Var")
+        return false;
+
+    return sameVariable(constructExp.Variable, destructExp.Variable);
+}
+
+function findMatchingConstructor(destructorEdge, body)
 {
-    for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
-        pbody.suppressed[id] = true;
+    var worklist = [destructorEdge];
+    var predecessors = getPredecessors(body);
+    while(worklist.length > 0) {
+        var edge = worklist.pop();
+        if (isMatchingConstructor(destructorEdge, edge))
+            return edge;
+        if (edge.Index[0] in predecessors) {
+            for (var e of predecessors[edge.Index[0]])
+                worklist.push(e);
+        }
+    }
+    printErr("Could not find matching constructor!");
+    debugger;
 }
+
+function pointsInRAIIScope(body, constructorEdge) {
+    var seen = {};
+    var worklist = [constructorEdge.Index[1]];
+    var points = [];
+    while (worklist.length) {
+        var point = worklist.pop();
+        if (point in seen)
+            continue;
+        seen[point] = true;
+        points.push([body, point]);
+        var successors = getSuccessors(body);
+        if (!(point in successors))
+            continue;
+        for (var nedge of successors[point]) {
+            if (isMatchingDestructor(constructorEdge, nedge))
+                continue;
+            if (nedge.Kind == "Loop")
+                Array.prototype.push.apply(points, findAllPoints(nedge.BlockId));
+            worklist.push(nedge.Index[1]);
+        }
+    }
+
+    return points;
+}
--- a/js/src/devtools/rootAnalysis/analyze.py
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -92,16 +92,17 @@ def generate_hazards(config, outfilename
     with open(outfilename, 'w') as output:
         command = ['cat'] + [ 'rootingHazards.%s' % (i+1,) for i in range(config['jobs']) ]
         print_command(command, outfile=outfilename)
         subprocess.call(command, stdout=output)
 
 JOBS = { 'dbs':
              (('%(ANALYSIS_SCRIPTDIR)s/run_complete',
                '--foreground',
+               '--no-logs',
                '--build-root=%(objdir)s',
                '--wrap-dir=%(sixgill)s/scripts/wrap_gcc',
                '--work-dir=work',
                '-b', '%(sixgill_bin)s',
                '--buildcommand=%(buildcommand)s',
                '.'),
               ()),
 
@@ -156,16 +157,17 @@ def run_job(name, config):
 
         command = list(cmdspec)
         outfile = 0
         for (i, name) in out_indexes(cmdspec):
             command[i] = '%s.tmp' % name
             temp_map[command[i]] = outfiles[outfile]
             outfile += 1
 
+        sys.stdout.flush()
         with open(temp, 'w') as output:
             subprocess.check_call(command, stdout=output, env=env(config))
         for (temp, final) in temp_map.items():
             try:
                 os.rename(temp, final)
             except OSError:
                 print("Error renaming %s -> %s" % (temp, final))
                 raise
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -1,15 +1,15 @@
 /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
-loadRelativeToScript('suppressedPoints.js');
+loadRelativeToScript('CFG.js');
 
 var sourceRoot = (environment['SOURCE'] || '') + '/'
 
 var functionBodies;
 
 if (typeof scriptArgs[0] != 'string' || typeof scriptArgs[1] != 'string')
     throw "Usage: analyzeRoots.js <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> [start end [tmpfile]]";
 
@@ -238,29 +238,16 @@ function edgeCanGC(edge)
         var fullFieldName = csuName + "." + field.Name[0];
         return fieldCallCannotGC(csuName, fullFieldName) ? null : fullFieldName;
     }
     assert(callee.Exp[0].Kind == "Var");
     var calleeName = callee.Exp[0].Variable.Name[0];
     return indirectCallCannotGC(functionName, calleeName) ? null : "*" + calleeName;
 }
 
-function computePredecessors(body)
-{
-    body.predecessors = [];
-    if (!("PEdge" in body))
-        return;
-    for (var edge of body.PEdge) {
-        var target = edge.Index[1];
-        if (!(target in body.predecessors))
-            body.predecessors[target] = [];
-        body.predecessors[target].push(edge);
-    }
-}
-
 function variableUseFollowsGC(suppressed, variable, worklist)
 {
     // Scan through all edges following an unrooted variable use, using an
     // explicit worklist. A worklist contains a following edge together with a
     // description of where one of its predecessors GC'd (if any).
 
     while (worklist.length) {
         var entry = worklist.pop();
@@ -568,15 +555,17 @@ var end = Math.min(minStream + each * ba
 for (var nameIndex = start; nameIndex <= end; nameIndex++) {
     var name = xdb.read_key(nameIndex);
     var functionName = name.readString();
     var data = xdb.read_entry(name);
     functionBodies = JSON.parse(data.readString());
 
     for (var body of functionBodies)
         body.suppressed = [];
-    for (var body of functionBodies)
-        computeSuppressedPoints(body);
+    for (var body of functionBodies) {
+        for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
+            pbody.suppressed[id] = true;
+    }
     processBodies(functionName);
 
     xdb.free_string(name);
     xdb.free_string(data);
 }
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -61,20 +61,20 @@ var ignoreClasses = {
 // a function pointer field named FIELD.
 var ignoreCallees = {
     "js::Class.trace" : true,
     "js::Class.finalize" : true,
     "JSRuntime.destroyPrincipals" : true,
     "nsISupports.AddRef" : true,
     "nsISupports.Release" : true, // makes me a bit nervous; this is a bug but can happen
     "nsAXPCNativeCallContext.GetJSContext" : true,
-    "js::ion::MDefinition.op" : true, // macro generated virtuals just return a constant
-    "js::ion::MDefinition.opName" : true, // macro generated virtuals just return a constant
-    "js::ion::LInstruction.getDef" : true, // virtual but no implementation can GC
-    "js::ion::IonCache.kind" : true, // macro generated virtuals just return a constant
+    "js::jit::MDefinition.op" : true, // macro generated virtuals just return a constant
+    "js::jit::MDefinition.opName" : true, // macro generated virtuals just return a constant
+    "js::jit::LInstruction.getDef" : true, // virtual but no implementation can GC
+    "js::jit::IonCache.kind" : true, // macro generated virtuals just return a constant
     "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
     "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
@@ -100,16 +100,18 @@ function ignoreEdgeUse(edge, variable)
     if (edge.Kind == "Call") {
         var callee = edge.Exp[0];
         if (callee.Kind == "Var") {
             var name = callee.Variable.Name[0];
             if (/~Anchor/.test(name))
                 return true;
             if (/~DebugOnly/.test(name))
                 return true;
+            if (/~ScopedThreadSafeStringInspector/.test(name))
+                return true;
         }
     }
 
     return false;
 }
 
 function ignoreEdgeAddressTaken(edge)
 {
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -1,15 +1,15 @@
 /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
-loadRelativeToScript('suppressedPoints.js');
+loadRelativeToScript('CFG.js');
 
 var subclasses = {};
 var superclasses = {};
 var classFunctions = {};
 
 function addClassEntry(index, name, other)
 {
     if (!(name in index)) {
@@ -225,18 +225,20 @@ var minStream = xdb.min_data_stream();
 var maxStream = xdb.max_data_stream();
 
 for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
     var name = xdb.read_key(nameIndex);
     var data = xdb.read_entry(name);
     functionBodies = JSON.parse(data.readString());
     for (var body of functionBodies)
         body.suppressed = [];
-    for (var body of functionBodies)
-        computeSuppressedPoints(body);
+    for (var body of functionBodies) {
+        for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
+            pbody.suppressed[id] = true;
+    }
 
     seenCallees = {};
     seenSuppressedCallees = {};
 
     for (var body of functionBodies)
         processBody(name.readString(), body);
 
     xdb.free_string(name);
--- a/js/src/devtools/rootAnalysis/run_complete
+++ b/js/src/devtools/rootAnalysis/run_complete
@@ -22,16 +22,17 @@
 # machines) and watching a shared poll_file for jobs. if the output directory
 # for this script already exists then an incremental analysis will be performed
 # and the reports will only reflect the changes since the earlier run.
 
 use strict;
 use IO::Handle;
 use File::Basename qw(dirname);
 use Getopt::Long;
+use Cwd;
 
 #################################
 # environment specific settings #
 #################################
 
 my $WORKDIR;
 my $SIXGILL_BIN;
 
@@ -50,18 +51,20 @@ my $ann_file = "";
 # optional output directory to do a diff against.
 my $old_dir = "";
 
 # run in the foreground
 my $foreground;
 
 my $builder = "make -j4";
 
+my $suppress_logs;
 GetOptions("build-root|b=s" => \$build_dir,
            "poll-file=s" => \$poll_file,
+           "no-logs!" => \$suppress_logs,
            "work-dir=s" => \$WORKDIR,
            "sixgill-binaries|binaries|b=s" => \$SIXGILL_BIN,
            "wrap-dir=s" => \$wrap_dir,
            "annotations-file|annotations|a=s" => \$ann_file,
            "old-dir|old=s" => \$old_dir,
            "foreground!" => \$foreground,
            "buildcommand=s" => \$builder,
            )
@@ -98,16 +101,23 @@ sub clean_project {
     system("make clean");
 }
 
 # code to build the project from $build_dir.
 sub build_project {
     return system($builder) >> 8;
 }
 
+our %kill_on_exit;
+END {
+    for my $pid (keys %kill_on_exit) {
+        kill($pid);
+    }
+}
+
 # commands to start the various xgill binaries. timeouts can be specified
 # for the backend analyses here, and a memory limit can be specified for
 # xmanager if desired (and USE_COUNT_ALLOCATOR is defined in util/alloc.h).
 my $xmanager = "$SIXGILL_BIN/xmanager";
 my $xsource = "$SIXGILL_BIN/xsource";
 my $xmemlocal = "$SIXGILL_BIN/xmemlocal -timeout=20";
 my $xinfer = "$SIXGILL_BIN/xinfer -timeout=60";
 my $xcheck = "$SIXGILL_BIN/xcheck -timeout=30";
@@ -138,36 +148,37 @@ if (not $foreground) {
 
 # if the result directory does not already exist, mark for a clean build.
 my $do_clean = 0;
 if (not (-d $result_dir)) {
     $do_clean = 1;
     mkdir $result_dir;
 }
 
-open(OUT, ">> $result_dir/complete.log");
-OUT->autoflush(1);  # don't buffer writes to the main log.
+if (!$suppress_logs) {
+    my $log_file = "$result_dir/complete.log";
+    open(OUT, ">>", $log_file) or die "append to $log_file: $!";
+    OUT->autoflush(1);  # don't buffer writes to the main log.
 
-# redirect stdout and stderr to the log.
-STDOUT->fdopen(\*OUT, "w");
-STDERR->fdopen(\*OUT, "w");
+    # redirect stdout and stderr to the log.
+    STDOUT->fdopen(\*OUT, "w");
+    STDERR->fdopen(\*OUT, "w");
+}
 
 # pids to wait on before exiting. these are collating worker output.
 my @waitpids;
 
 chdir $result_dir;
 
 # to do a partial run, comment out the commands here you don't want to do.
 
 my $status = run_build();
 
 # end of run commands.
 
-close(OUT);
-
 for my $pid (@waitpids) {
     waitpid($pid, 0);
     $status ||= $? >> 8;
 }
 
 print "Exiting run_complete with status $status\n";
 exit $status;
 
@@ -192,30 +203,33 @@ sub run_build
     print "build started: ";
     print scalar(localtime());
     print "\n";
 
     # fork off a process to run the build.
     defined(my $pid = fork) or die;
 
     # log file for the manager.
-    my $log_file = "$result_dir/build_manager.log";
+    my $manager_log_file = "$result_dir/build_manager.log";
 
     if (!$pid) {
         # this is the child process, fork another process to run a manager.
         defined(my $pid = fork) or die;
-        exec("$xmanager -terminate-on-assert > $log_file 2>&1") if (!$pid);
+        exec("$xmanager -terminate-on-assert > $manager_log_file 2>&1") if (!$pid);
+        $kill_on_exit{$pid} = 1;
 
-        # open new streams to redirect stdout and stderr.
-        open(LOGOUT, "> $result_dir/build.log");
-        open(LOGERR, "> $result_dir/build_err.log");
-        STDOUT->fdopen(\*LOGOUT, "w");
-        STDERR->fdopen(\*LOGERR, "w");
+        if (!$suppress_logs) {
+            # open new streams to redirect stdout and stderr.
+            open(LOGOUT, "> $result_dir/build.log");
+            open(LOGERR, "> $result_dir/build_err.log");
+            STDOUT->fdopen(\*LOGOUT, "w");
+            STDERR->fdopen(\*LOGERR, "w");
+        }
 
-        my $address = get_manager_address($log_file);
+        my $address = get_manager_address($manager_log_file);
 
         # write the configuration file for the wrapper script.
         my $config_file = "$WORKDIR/xgill.config";
         open(CONFIG, ">", $config_file) or die "create $config_file: $!";
         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"
@@ -234,21 +248,23 @@ sub run_build
         my $exit_status = build_project();
 
         # signal the manager that it's over.
         system("$xsource -remote=$address -end-manager");
 
         # wait for the manager to clean up and terminate.
         print "Waiting for manager to finish (build status $exit_status)...\n";
         waitpid($pid, 0);
+        my $manager_status = $?;
+        delete $kill_on_exit{$pid};
 
         # build is finished, the complete run can resume.
         # return value only useful if --foreground
-        print "Exiting with status " . ($? || $exit_status) . "\n";
-        exit($? || $exit_status);
+        print "Exiting with status " . ($manager_status || $exit_status) . "\n";
+        exit($manager_status || $exit_status);
     }
 
     # this is the complete process, wait for the build to finish.
     waitpid($pid, 0);
     my $status = $? >> 8;
     print "build finished (status $status): ";
     print scalar(localtime());
     print "\n";