Backed out 22 changesets (bug 1259850) for GC crashes in various tests CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Tue, 31 May 2016 11:15:41 -0700
changeset 299791 69518db96a4dd3e483a679a0e8e628184c7c218a
parent 299790 26187322aa0533fc983cacbd208a2922a897e2ec
child 299792 e04f158343d2d3709977f46131a7752d81a7babe
push id19470
push usercbook@mozilla.com
push dateWed, 01 Jun 2016 13:09:28 +0000
treeherderfx-team@22047a4eea78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1259850
milestone49.0a1
backs outef5cdcca45d9c9835a72d76e6e8e3491c5d62289
c95bdd426ced9a71bc64694ea236b46d035bb0df
a73f74f718e7464d40c17df3f4228c96d5819d1a
95107c3ad9cf5a173a17aee4fcd8635defbf0eb4
788ac18818c9b91f3057c4c382bf17be264eb03e
19c13aa9b5ada4d1284a09d8284b3231e0790ebe
0b9dedcf7163ce35e4207a1d8fe33523a9a8f920
b641d01138ab54ec1f6b29232614b338b02d42f1
aa434447a11bd8b62b954b24005729a1bd757679
4c7373c6c29efffa4e7f1f87acbf755d9fae8f0d
457cb29cad5556b8289deb94c67590d9c2b23c9f
5762a8fba027bb667a621deb50540ddd5a884193
129559d4ac621b3801e41ce10db1cb4b1a6786da
d00b9c8a7984a8e117eb191280b08b1aed879710
266befcb8acd57bc12a5949d6c873521d7ea6167
c6615c7b00838e1d212c6007b4a54b20d71f7ae0
196ac1f813f9a9489d24ba0c70c1dfb20b404489
b6108a65dc38d298be19b74a30dc683bec69a3b3
0d58f8529b86a129f178788bd056d2a60cc73319
a8d2730ada95c70059ed6314f5eb3f7b28501f70
e8544b072ee6b0055984ad7307216d61d4d3a26f
15de0d1d0b05457c18dcf70e4e81d9bfe5ad5f79
Backed out 22 changesets (bug 1259850) for GC crashes in various tests CLOSED TREE Backed out changeset ef5cdcca45d9 (bug 1259850) Backed out changeset c95bdd426ced (bug 1259850) Backed out changeset a73f74f718e7 (bug 1259850) Backed out changeset 95107c3ad9cf (bug 1259850) Backed out changeset 788ac18818c9 (bug 1259850) Backed out changeset 19c13aa9b5ad (bug 1259850) Backed out changeset 0b9dedcf7163 (bug 1259850) Backed out changeset b641d01138ab (bug 1259850) Backed out changeset aa434447a11b (bug 1259850) Backed out changeset 4c7373c6c29e (bug 1259850) Backed out changeset 457cb29cad55 (bug 1259850) Backed out changeset 5762a8fba027 (bug 1259850) Backed out changeset 129559d4ac62 (bug 1259850) Backed out changeset d00b9c8a7984 (bug 1259850) Backed out changeset 266befcb8acd (bug 1259850) Backed out changeset c6615c7b0083 (bug 1259850) Backed out changeset 196ac1f813f9 (bug 1259850) Backed out changeset b6108a65dc38 (bug 1259850) Backed out changeset 0d58f8529b86 (bug 1259850) Backed out changeset a8d2730ada95 (bug 1259850) Backed out changeset e8544b072ee6 (bug 1259850) Backed out changeset 15de0d1d0b05 (bug 1259850)
.hgignore
b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
browser/config/tooltool-manifests/linux64/hazard.manifest
js/public/GCAPI.h
js/public/GCAnnotations.h
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/build/sixgill.manifest
js/src/devtools/rootAnalysis/computeCallgraph.js
js/src/devtools/rootAnalysis/computeGCFunctions.js
js/src/devtools/rootAnalysis/computeGCTypes.js
js/src/devtools/rootAnalysis/explain.py
js/src/devtools/rootAnalysis/loadCallgraph.js
js/src/devtools/rootAnalysis/run-test.py
js/src/devtools/rootAnalysis/t/exceptions/source.cpp
js/src/devtools/rootAnalysis/t/exceptions/test.py
js/src/devtools/rootAnalysis/t/hazards/source.cpp
js/src/devtools/rootAnalysis/t/hazards/test.py
js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp
js/src/devtools/rootAnalysis/t/sixgill-tree/test.py
js/src/devtools/rootAnalysis/t/sixgill.py
js/src/devtools/rootAnalysis/t/suppression/source.cpp
js/src/devtools/rootAnalysis/t/suppression/test.py
js/src/devtools/rootAnalysis/t/testlib.py
js/src/devtools/rootAnalysis/test/source.cpp
js/src/devtools/rootAnalysis/test/test.py
js/src/devtools/rootAnalysis/utility.js
js/src/gc/Iteration.cpp
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jit/BaselineJIT.cpp
js/src/jit/Ion.cpp
js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
js/src/jscompartment.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
js/src/jsopcode.cpp
js/src/vm/Debugger.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/NativeObject.cpp
js/src/vm/TypeInference.cpp
--- a/.hgignore
+++ b/.hgignore
@@ -38,17 +38,16 @@
 ^js/src/.*-obj/
 
 # SpiderMonkey configury
 ^js/src/configure$
 ^js/src/old-configure$
 ^js/src/autom4te.cache$
 # SpiderMonkey test result logs
 ^js/src/tests/results-.*\.(html|txt)$
-^js/src/devtools/rootAnalysis/t/out
 
 # Java HTML5 parser classes
 ^parser/html/java/(html|java)parser/
 
 # SVN directories
 \.svn/
 
 # Ignore the files and directory that Eclipse IDE creates
--- a/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
+++ b/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
@@ -1,47 +1,47 @@
 [
 {
-"size" : 102421980,
-"digest" : "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
-"version" : "gcc 4.9.3",
-"unpack" : true,
-"filename" : "gcc.tar.xz",
-"algorithm" : "sha512"
+"version": "gcc 4.9.3",
+"size": 102421980,
+"digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
 },
 {
-"unpack" : true,
-"algorithm" : "sha512",
-"filename" : "sixgill.tar.xz",
-"hg_id" : "8cb9c3fb039a+ tip",
-"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
-"size" : 2631908
-},
-{
+"hg_id" : "cd93f15a30ce",
 "algorithm" : "sha512",
-"filename" : "gtk3.tar.xz",
-"setup" : "setup.sh",
-"unpack" : true,
-"digest" : "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
-"size" : 12072532
-},
-{
-"size" : 89319524,
-"digest" : "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
-"algorithm" : "sha512",
-"filename" : "rustc.tar.xz",
+"digest" : "541eb3842ab6b91bd87223cad7a5e4387ef3e496e5b580c8047b8b586bc7eb69fecf3c9eb8c45a1e0deebb53554f0e8acedfe1b4ca64d93b6d008f3f2eb11389",
+"filename" : "sixgill.tar.xz",
+"size" : 2626640,
 "unpack" : true
 },
 {
-"algorithm" : "sha512",
-"filename" : "sccache.tar.bz2",
-"unpack" : true,
-"digest" : "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"size" : 167175
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
+"size": 89319524,
+"digest": "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
 },
 {
-"filename" : "moz-tt.tar.bz2",
-"algorithm" : "sha512",
-"unpack" : true,
-"digest" : "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
-"size" : 31078810
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"algorithm": "sha512",
+"filename": "moz-tt.tar.bz2",
+"unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/linux64/hazard.manifest
+++ b/browser/config/tooltool-manifests/linux64/hazard.manifest
@@ -1,40 +1,40 @@
 [
 {
-"size" : 102421980,
-"version" : "gcc 4.9.3",
-"filename" : "gcc.tar.xz",
+"version": "gcc 4.9.3",
+"size": 102421980,
+"digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"hg_id" : "cd93f15a30ce",
 "algorithm" : "sha512",
-"digest" : "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+"digest" : "541eb3842ab6b91bd87223cad7a5e4387ef3e496e5b580c8047b8b586bc7eb69fecf3c9eb8c45a1e0deebb53554f0e8acedfe1b4ca64d93b6d008f3f2eb11389",
+"filename" : "sixgill.tar.xz",
+"size" : 2626640,
 "unpack" : true
 },
 {
-"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
-"unpack" : true,
-"algorithm" : "sha512",
-"filename" : "sixgill.tar.xz",
-"size" : 2631908,
-"hg_id" : "8cb9c3fb039a+ tip"
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
 },
 {
-"digest" : "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
-"unpack" : true,
-"setup" : "setup.sh",
-"algorithm" : "sha512",
-"filename" : "gtk3.tar.xz",
-"size" : 12072532
+"size": 89319524,
+"digest": "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
 },
 {
-"unpack" : true,
-"digest" : "5383d843c9f28abf0a6d254e9d975d96972d2c86d627ca836fa8e272a5d53230603b387d7d1499c49df7f84b1bb946946e800a85c88d968bdbe81c755fcb02e1",
-"filename" : "rustc.tar.xz",
-"algorithm" : "sha512",
-"size" : 89319524
-},
-{
-"filename" : "sccache.tar.bz2",
-"algorithm" : "sha512",
-"digest" : "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"unpack" : true,
-"size" : 167175
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
 }
 ]
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -546,17 +546,17 @@ class JS_PUBLIC_API(AutoAssertNoAlloc)
  *       that the hazard analysis is correct for that code, rather than relying
  *       on this class.
  */
 class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertNoAlloc
 {
   public:
     AutoSuppressGCAnalysis() : AutoAssertNoAlloc() {}
     explicit AutoSuppressGCAnalysis(JSRuntime* rt) : AutoAssertNoAlloc(rt) {}
-} JS_HAZ_GC_SUPPRESSED;
+};
 
 /**
  * Assert that code is only ever called from a GC callback, disable the static
  * rooting hazard analysis and assert if any allocation that could potentially
  * trigger a GC occurs while this guard object is live.
  *
  * This is useful to make the static analysis ignore code that runs in GC
  * callbacks.
--- a/js/public/GCAnnotations.h
+++ b/js/public/GCAnnotations.h
@@ -18,40 +18,36 @@
 // 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 not itself a GC pointer.
+// 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")))
 
-// Mark an RAII class as suppressing GC within its scope.
-# define JS_HAZ_GC_SUPPRESSED __attribute__((tag("Suppress GC")))
-
 #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
-# define JS_HAZ_GC_SUPPRESSED
 
 #endif
 
 #endif /* js_GCAnnotations_h */
--- a/js/src/devtools/rootAnalysis/CFG.js
+++ b/js/src/devtools/rootAnalysis/CFG.js
@@ -65,17 +65,17 @@ function allRAIIGuardedCallPoints(bodies
     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(edge.Type, variable.Name))
+        if (!isConstructor(variable.Name))
             continue;
         if (!("PEdgeCallInstance" in edge))
             continue;
         if (edge.PEdgeCallInstance.Exp.Kind != "Var")
             continue;
 
         Array.prototype.push.apply(points, pointsInRAIIScope(bodies, body, edge));
     }
--- a/js/src/devtools/rootAnalysis/analyze.py
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -67,39 +67,36 @@ def generate_hazards(config, outfilename
     jobs = []
     for i in range(int(config['jobs'])):
         command = fill(('%(js)s',
                         '%(analysis_scriptdir)s/analyzeRoots.js',
                         '%(gcFunctions_list)s',
                         '%(gcEdges)s',
                         '%(suppressedFunctions_list)s',
                         '%(gcTypes)s',
-                        '%(typeInfo)s',
                         str(i+1), '%(jobs)s',
                         'tmp.%s' % (i+1,)),
                        config)
         outfile = 'rootingHazards.%s' % (i+1,)
         output = open(outfile, 'w')
-        if config['verbose']:
-            print_command(command, outfile=outfile, env=env(config))
+        print_command(command, outfile=outfile, env=env(config))
         jobs.append((command, Popen(command, stdout=output, env=env(config))))
 
     final_status = 0
     while jobs:
         pid, status = os.wait()
         jobs = [ job for job in jobs if job[1].pid != pid ]
         final_status = final_status or status
 
     if final_status:
         raise subprocess.CalledProcessError(final_status, 'analyzeRoots.js')
 
     with open(outfilename, 'w') as output:
         command = ['cat'] + [ 'rootingHazards.%s' % (i+1,) for i in range(int(config['jobs'])) ]
-        if config['verbose']:
-            print_command(command, outfile=outfilename)
+        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',
@@ -109,28 +106,27 @@ JOBS = { 'dbs':
                '.'),
               ()),
 
          'list-dbs':
              (('ls', '-l'),
               ()),
 
          'callgraph':
-             (('%(js)s', '%(analysis_scriptdir)s/computeCallgraph.js', '%(typeInfo)s'),
+             (('%(js)s', '%(analysis_scriptdir)s/computeCallgraph.js'),
               'callgraph.txt'),
 
          'gcFunctions':
              (('%(js)s', '%(analysis_scriptdir)s/computeGCFunctions.js', '%(callgraph)s',
                '[gcFunctions]', '[gcFunctions_list]', '[gcEdges]', '[suppressedFunctions_list]'),
               ('gcFunctions.txt', 'gcFunctions.lst', 'gcEdges.txt', 'suppressedFunctions.lst')),
 
          'gcTypes':
-             (('%(js)s', '%(analysis_scriptdir)s/computeGCTypes.js',
-               '[gcTypes]', '[typeInfo]'),
-              ('gcTypes.txt', 'typeInfo.txt')),
+             (('%(js)s', '%(analysis_scriptdir)s/computeGCTypes.js',),
+              'gcTypes.txt'),
 
          'allFunctions':
              (('%(sixgill_bin)s/xdbkeys', 'src_body.xdb',),
               'allFunctions.txt'),
 
          'hazards':
              (generate_hazards, 'rootingHazards.txt'),
 
@@ -154,27 +150,25 @@ def run_job(name, config):
     if hasattr(cmdspec, '__call__'):
         cmdspec(config, outfiles)
     else:
         temp_map = {}
         cmdspec = fill(cmdspec, config)
         if isinstance(outfiles, basestring):
             stdout_filename = '%s.tmp' % name
             temp_map[stdout_filename] = outfiles
-            if config['verbose']:
-                print_command(cmdspec, outfile=outfiles, env=env(config))
+            print_command(cmdspec, outfile=outfiles, env=env(config))
         else:
             stdout_filename = None
             pc = list(cmdspec)
             outfile = 0
             for (i, name) in out_indexes(cmdspec):
                 pc[i] = outfiles[outfile]
                 outfile += 1
-            if config['verbose']:
-                print_command(pc, env=env(config))
+            print_command(pc, env=env(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
 
@@ -191,16 +185,25 @@ def run_job(name, config):
                 print("Error renaming %s -> %s" % (temp, final))
                 raise
 
 config = { 'ANALYSIS_SCRIPTDIR': os.path.dirname(__file__) }
 
 defaults = [ '%s/defaults.py' % config['ANALYSIS_SCRIPTDIR'],
              '%s/defaults.py' % os.getcwd() ]
 
+for default in defaults:
+    try:
+        execfile(default, config)
+        print("Loaded %s" % default)
+    except:
+        pass
+
+data = config.copy()
+
 parser = argparse.ArgumentParser(description='Statically analyze build tree for rooting hazards.')
 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='?',
@@ -212,42 +215,29 @@ parser.add_argument('--jobs', '-j', defa
 parser.add_argument('--list', const=True, nargs='?', type=bool,
                     help='display available steps')
 parser.add_argument('--buildcommand', '--build', '-b', type=str, nargs='?',
                     help='command to build the tree being analyzed')
 parser.add_argument('--tag', '-t', type=str, nargs='?',
                     help='name of job, also sets build command to "build.<tag>"')
 parser.add_argument('--expect-file', type=str, nargs='?',
                     help='deprecated option, temporarily still present for backwards compatibility')
-parser.add_argument('--verbose', '-v', action='store_true',
-                    help='Display cut & paste commands to run individual steps')
 
 args = parser.parse_args()
-
-for default in defaults:
-    try:
-        execfile(default, config)
-        if args.verbose:
-            print("Loaded %s" % default)
-    except:
-        pass
-
-data = config.copy()
-
 for k,v in vars(args).items():
     if v is not None:
         data[k] = v
 
 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']).strip()
+    data['jobs'] = subprocess.check_output(['nproc', '--ignore=1'])
 
 if args.buildcommand:
     data['buildcommand'] = args.buildcommand
 elif 'BUILD' in os.environ:
     data['buildcommand'] = os.environ['BUILD']
 else:
     data['buildcommand'] = 'make -j4 -s'
 
@@ -256,18 +246,18 @@ 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',
           'gcTypes',
-          'callgraph',
           'gcFunctions',
           'allFunctions',
           'hazards',
           'explain' ]
 
 if args.list:
     for step in steps:
         command, outfilename = JOBS[step]
@@ -281,17 +271,17 @@ for step in steps:
     command, outfiles = JOBS[step]
     if isinstance(outfiles, basestring):
         data[step] = outfiles
     else:
         outfile = 0
         for (i, name) in out_indexes(command):
             data[name] = outfiles[outfile]
             outfile += 1
-        assert len(outfiles) == outfile, 'step \'%s\': mismatched number of output files (%d) and params (%d)' % (step, outfile, len(outfiles))
+        assert len(outfiles) == outfile, 'step \'%s\': mismatched number of output files and params' % step
 
 if args.step:
     steps = steps[steps.index(args.step):]
 
 if args.upto:
     steps = steps[:steps.index(args.upto)+1]
 
 for step in steps:
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -7,34 +7,31 @@ loadRelativeToScript('annotations.js');
 loadRelativeToScript('CFG.js');
 
 var sourceRoot = (os.getenv('SOURCE') || '') + '/'
 
 var functionName;
 var functionBodies;
 
 if (typeof scriptArgs[0] != 'string' || typeof scriptArgs[1] != 'string')
-    throw "Usage: analyzeRoots.js [-f function_name] <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> <typeInfo.txt> [start end [tmpfile]]";
+    throw "Usage: analyzeRoots.js [-f function_name] <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> [start end [tmpfile]]";
 
 var theFunctionNameToFind;
 if (scriptArgs[0] == '--function') {
     theFunctionNameToFind = scriptArgs[1];
     scriptArgs = scriptArgs.slice(2);
 }
 
-var gcFunctionsFile = scriptArgs[0] || "gcFunctions.lst";
-var gcEdgesFile = scriptArgs[1] || "gcEdges.txt";
-var suppressedFunctionsFile = scriptArgs[2] || "suppressedFunctions.lst";
-var gcTypesFile = scriptArgs[3] || "gcTypes.txt";
-var typeInfoFile = scriptArgs[4] || "typeInfo.txt";
-var batch = (scriptArgs[5]|0) || 1;
-var numBatches = (scriptArgs[6]|0) || 1;
-var tmpfile = scriptArgs[7] || "tmp.txt";
-
-GCSuppressionTypes = loadTypeInfo(typeInfoFile)["Suppress GC"] || [];
+var gcFunctionsFile = scriptArgs[0];
+var gcEdgesFile = scriptArgs[1];
+var suppressedFunctionsFile = scriptArgs[2];
+var gcTypesFile = scriptArgs[3];
+var batch = (scriptArgs[4]|0) || 1;
+var numBatches = (scriptArgs[5]|0) || 1;
+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[mangled(line)] = true;
 
 var suppressedFunctions = {};
@@ -329,67 +326,41 @@ function edgeCanGC(edge)
 //
 // If so:
 //
 //  - 'why': a path from the GC call to a use of the variable after the GC
 //    call, chained through a 'why' field in the returned edge descriptor
 //
 //  - 'gcInfo': a direct pointer to the GC call edge
 //
-function findGCBeforeVariableUse(start_body, start_point, suppressed, variable)
+function findGCBeforeVariableUse(suppressed, variable, worklist)
 {
     // Scan through all edges preceding an unrooted variable use, using an
     // explicit worklist, looking for a GC call. A worklist contains an
     // incoming edge together with a description of where it or one of its
     // successors GC'd (if any).
 
-    var bodies_visited = new Map();
-
-    let worklist = [{body: start_body, ppoint: start_point, preGCLive: false, gcInfo: null, why: null}];
     while (worklist.length) {
-        // Grab an entry off of the worklist, representing a point within the
-        // CFG identified by <body,ppoint>. If this point has a descendant
-        // later in the CFG that can GC, gcInfo will be set to the information
-        // about that GC call.
-
         var entry = worklist.pop();
-        var { body, ppoint, gcInfo, preGCLive } = entry;
-
-        // Handle the case where there are multiple ways to reach this point
-        // (traversing backwards).
-        var visited = bodies_visited.get(body);
-        if (!visited)
-            bodies_visited.set(body, visited = new Map());
-        if (visited.has(ppoint)) {
-            var seenEntry = visited.get(ppoint);
+        var { body, ppoint, gcInfo } = entry;
 
-            // This point already knows how to GC through some other path, so
-            // we have nothing new to learn. (The other path will consider the
-            // predecessors.)
-            if (seenEntry.gcInfo)
-                continue;
+        if (body.seen) {
+            if (ppoint in body.seen) {
+                var seenEntry = body.seen[ppoint];
+                if (!gcInfo || seenEntry.gcInfo)
+                    continue;
+            }
+        } else {
+            body.seen = [];
+        }
+        body.seen[ppoint] = {body: body, gcInfo: gcInfo};
 
-            // If this worklist's entry doesn't know of any way to GC, then
-            // there's no point in continuing the traversal through it. Perhaps
-            // another edge will be found that *can* GC; otherwise, the first
-            // route to the point will traverse through predecessors.
-            //
-            // Note that this means we may visit a point more than once, if the
-            // first time we visit we don't have a known reachable GC call and
-            // the second time we do.
-            if (!gcInfo)
-                continue;
-        }
-        visited.set(ppoint, {body: body, gcInfo: gcInfo});
-
-        // Check for hitting the entry point of the current body (which may be
-        // the outer function or a loop within it.)
         if (ppoint == body.Index[0]) {
             if (body.BlockId.Kind == "Loop") {
-                // Propagate to outer body parents that enter the loop body.
+                // propagate to parents that enter the loop body.
                 if ("BlockPPoint" in body) {
                     for (var parent of body.BlockPPoint) {
                         var found = false;
                         for (var xbody of functionBodies) {
                             if (sameBlockId(xbody.BlockId, parent.BlockId)) {
                                 assert(!found);
                                 found = true;
                                 worklist.push({body: xbody, ppoint: parent.Index,
@@ -397,23 +368,17 @@ function findGCBeforeVariableUse(start_b
                             }
                         }
                         assert(found);
                     }
                 }
             } else if (variable.Kind == "Arg" && gcInfo) {
                 // The scope of arguments starts at the beginning of the
                 // function
-                return entry;
-            } else if (entry.preGCLive) {
-                // We didn't find a "good" explanation beginning of the live
-                // range, but we do know the variable was live across the GC.
-                // This can happen if the live range started when a variable is
-                // used as a retparam.
-                return entry;
+                return {gcInfo: gcInfo, why: entry};
             }
         }
 
         var predecessors = getPredecessors(body);
         if (!(ppoint in predecessors))
             continue;
 
         for (var edge of predecessors[ppoint]) {
@@ -429,114 +394,86 @@ function findGCBeforeVariableUse(start_b
 
             if (edge_kills) {
                 // This is a beginning of the variable's live range. If we can
                 // reach a GC call from here, then we're done -- we have a path
                 // from the beginning of the live range, through the GC call,
                 // to a use after the GC call that proves its live range
                 // extends at least that far.
                 if (gcInfo)
-                    return {body: body, ppoint: source, gcInfo: gcInfo, why: entry };
+                    return {gcInfo: gcInfo, why: {body: body, ppoint: source, gcInfo: gcInfo, why: entry } }
 
-                // Otherwise, keep searching through the graph, but truncate
-                // this particular branch of the search at this edge.
+                // Otherwise, we want to continue searching for the true
+                // minimumUse, for use in reporting unnecessary rooting, but we
+                // truncate this particular branch of the search at this edge.
                 continue;
             }
 
-            var src_gcInfo = gcInfo;
-            var src_preGCLive = preGCLive;
             if (!gcInfo && !(source in body.suppressed) && !suppressed) {
                 var gcName = edgeCanGC(edge, body);
                 if (gcName)
-                    src_gcInfo = {name:gcName, body:body, ppoint:source};
+                    gcInfo = {name:gcName, body:body, ppoint:source};
             }
 
             if (edge_uses) {
                 // The live range starts at least this far back, so we're done
-                // for the same reason as with edge_kills. The only difference
-                // is that a GC on this edge indicates a hazard, whereas if
-                // we're killing a live range in the GC call then it's not live
-                // *across* the call.
-                //
-                // However, we may want to generate a longer usage chain for
-                // the variable than is minimally necessary. For example,
-                // consider:
-                //
-                //   Value v = f();
-                //   if (v.isUndefined())
-                //     return false;
-                //   gc();
-                //   return v;
-                //
-                // The call to .isUndefined() is considered to be a use and
-                // therefore indicates that v must be live at that point. But
-                // it's more helpful to the user to continue the 'why' path to
-                // include the ancestor where the value was generated. So we
-                // will only return here if edge.Kind is Assign; otherwise,
-                // we'll pass a "preGCLive" value up through the worklist to
-                // remember that the variable *is* alive before the GC and so
-                // this function should be returning a true value.
-
-                if (src_gcInfo) {
-                    src_preGCLive = true;
-                    if (edge.Kind == 'Assign')
-                        return {body:body, ppoint:source, gcInfo:src_gcInfo, why:entry};
-                }
+                // for the same reason as with edge_kills.
+                if (gcInfo)
+                    return {gcInfo:gcInfo, why:entry};
             }
 
             if (edge.Kind == "Loop") {
                 // Additionally propagate the search into a loop body, starting
                 // with the exit point.
                 var found = false;
                 for (var xbody of functionBodies) {
                     if (sameBlockId(xbody.BlockId, edge.BlockId)) {
                         assert(!found);
                         found = true;
                         worklist.push({body:xbody, ppoint:xbody.Index[1],
-                                       preGCLive: src_preGCLive, gcInfo:src_gcInfo,
-                                       why:entry});
+                                       gcInfo:gcInfo, why:entry});
                     }
                 }
                 assert(found);
                 break;
             }
 
             // Propagate the search to the predecessors of this edge.
-            worklist.push({body:body, ppoint:source,
-                           preGCLive: src_preGCLive, gcInfo:src_gcInfo,
-                           why:entry});
+            worklist.push({body:body, ppoint:source, gcInfo:gcInfo, why:entry});
         }
     }
 
     return null;
 }
 
 function variableLiveAcrossGC(suppressed, variable)
 {
-    // A variable is live across a GC if (1) it is used by an edge (as in, it
-    // was at least initialized), and (2) it is used after a GC in a successor
-    // edge.
+    // A variable is live across a GC if (1) it is used by an edge, and (2) it
+    // is used after a GC in a successor edge.
 
-    for (var body of functionBodies)
+    for (var body of functionBodies) {
+        body.seen = null;
         body.minimumUse = 0;
+    }
 
     for (var body of functionBodies) {
         if (!("PEdge" in body))
             continue;
         for (var edge of body.PEdge) {
             var usePoint = edgeUsesVariable(edge, variable, body);
             // Example for !edgeKillsVariable:
             //
             //   JSObject* obj = NewObject();
             //   cangc();
             //   obj = NewObject();    <-- uses 'obj', but kills previous value
             //
             if (usePoint && !edgeKillsVariable(edge, variable)) {
                 // Found a use, possibly after a GC.
-                var call = findGCBeforeVariableUse(body, usePoint, suppressed, variable);
+                var worklist = [{body:body, ppoint:usePoint, gcInfo:null, why:null}];
+                var call = findGCBeforeVariableUse(suppressed, variable, worklist);
                 if (!call)
                     continue;
 
                 call.afterGCUse = usePoint;
                 return call;
             }
         }
     }
@@ -559,19 +496,17 @@ function unsafeVariableAddressTaken(supp
                 if (edge.Kind == "Assign" || (!suppressed && edgeCanGC(edge)))
                     return {body:body, ppoint:edge.Index[0]};
             }
         }
     }
     return null;
 }
 
-// Read out the brief (non-JSON, semi-human-readable) CFG description for the
-// given function and store it.
-function loadPrintedLines(functionName)
+function computePrintedLines(functionName)
 {
     assert(!os.system("xdbfind src_body.xdb '" + functionName + "' > " + tmpfile));
     var lines = snarf(tmpfile).split('\n');
 
     for (var body of functionBodies)
         body.lines = [];
 
     // Distribute lines of output to the block they originate from.
@@ -596,62 +531,54 @@ function loadPrintedLines(functionName)
                 }
             }
         }
         if (currentBody)
             currentBody.lines.push(line);
     }
 }
 
-function findLocation(body, ppoint, opts={brief: false})
+function findLocation(body, ppoint)
 {
     var location = body.PPoint[ppoint - 1].Location;
-    var file = location.CacheString;
-
-    if (file.indexOf(sourceRoot) == 0)
-        file = file.substring(sourceRoot.length);
-
-    if (opts.brief) {
-        var m = /.*\/(.*)/.exec(file);
-        if (m)
-            file = m[1];
-    }
-
-    return file + ":" + location.Line;
+    var text = location.CacheString + ":" + location.Line;
+    if (text.indexOf(sourceRoot) == 0)
+        return text.substring(sourceRoot.length);
+    return text;
 }
 
 function locationLine(text)
 {
     if (match = /:(\d+)$/.exec(text))
         return match[1];
     return 0;
 }
 
 function printEntryTrace(functionName, entry)
 {
     var gcPoint = entry.gcInfo ? entry.gcInfo.ppoint : 0;
 
     if (!functionBodies[0].lines)
-        loadPrintedLines(functionName);
+        computePrintedLines(functionName);
 
     while (entry) {
         var ppoint = entry.ppoint;
-        var lineText = findLocation(entry.body, ppoint, {"brief": true});
+        var lineText = findLocation(entry.body, ppoint);
 
         var edgeText = "";
         if (entry.why && entry.why.body == entry.body) {
             // If the next point in the trace is in the same block, look for an edge between them.
             var next = entry.why.ppoint;
 
             if (!entry.body.edgeTable) {
                 var table = {};
                 entry.body.edgeTable = table;
                 for (var line of entry.body.lines) {
-                    if (match = /\((\d+,\d+),/.exec(line))
-                        table[match[1]] = line; // May be multiple?
+                    if (match = /\((\d+),(\d+),/.exec(line))
+                        table[match[1] + "," + match[2]] = line; // May be multiple?
                 }
             }
 
             edgeText = entry.body.edgeTable[ppoint + "," + next];
             assert(edgeText);
             if (ppoint == gcPoint)
                 edgeText += " [[GC call]]";
         } else {
@@ -727,17 +654,17 @@ function processBodies(functionName)
             var result = variableLiveAcrossGC(suppressed, variable.Variable);
             if (result) {
                 var lineText = findLocation(result.gcInfo.body, result.gcInfo.ppoint);
                 print("\nFunction '" + functionName + "'" +
                       " has unrooted '" + name + "'" +
                       " of type '" + typeDesc(variable.Type) + "'" +
                       " live across GC call " + result.gcInfo.name +
                       " at " + lineText);
-                printEntryTrace(functionName, result);
+                printEntryTrace(functionName, result.why);
             }
             result = unsafeVariableAddressTaken(suppressed, variable.Variable);
             if (result) {
                 var lineText = findLocation(result.body, result.ppoint);
                 print("\nFunction '" + functionName + "'" +
                       " takes unsafe address of unrooted '" + name + "'" +
                       " at " + lineText);
                 printEntryTrace(functionName, {body:result.body, ppoint:result.ppoint});
@@ -751,19 +678,19 @@ if (batch == 1)
 
 var xdb = xdbLibrary();
 xdb.open("src_body.xdb");
 
 var minStream = xdb.min_data_stream()|0;
 var maxStream = xdb.max_data_stream()|0;
 
 var N = (maxStream - minStream) + 1;
-var start = Math.floor((batch - 1) / numBatches * N) + minStream;
-var start_next = Math.floor(batch / numBatches * N) + minStream;
-var end = start_next - 1;
+var each = Math.floor(N/numBatches);
+var start = minStream + each * (batch - 1);
+var end = Math.min(minStream + each * batch - 1, maxStream);
 
 function process(name, json) {
     functionName = name;
     functionBodies = JSON.parse(json);
 
     for (var body of functionBodies)
         body.suppressed = [];
     for (var body of functionBodies) {
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -1,16 +1,12 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
-// RAII types within which we should assume GC is suppressed, eg
-// AutoSuppressGC.
-var GCSuppressionTypes = [];
-
 // Ignore calls made through these function pointers
 var ignoreIndirectCalls = {
     "mallocSizeOf" : true,
     "aMallocSizeOf" : true,
     "_malloc_message" : true,
     "je_malloc_message" : true,
     "chunk_dalloc" : true,
     "chunk_alloc" : true,
@@ -80,17 +76,16 @@ var ignoreCallees = {
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "PLDHashTableOps.hashKey" : true,
     "z_stream_s.zfree" : true,
     "z_stream_s.zalloc" : true,
     "GrGLInterface.fCallback" : true,
     "std::strstreambuf._M_alloc_fun" : true,
     "std::strstreambuf._M_free_fun" : true,
     "struct js::gc::Callback<void (*)(JSRuntime*, void*)>.op" : true,
-    "mozilla::ThreadSharedFloatArrayBufferList::Storage.mFree" : true,
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
         return true;
     if (fullfield in ignoreCallees)
         return true;
@@ -146,27 +141,20 @@ function ignoreEdgeAddressTaken(edge)
             if (/js::Invoke\(/.test(name))
                 return true;
         }
     }
 
     return false;
 }
 
-// Return whether csu.method is one that we claim can never GC.
-function isSuppressedVirtualMethod(csu, method)
-{
-    return csu == "nsISupports" && (method == "AddRef" || method == "Release");
-}
-
 // Ignore calls of these functions (so ignore any stack containing these)
 var ignoreFunctions = {
     "ptio.c:pt_MapError" : true,
     "je_malloc_printf" : true,
-    "vprintf_stderr" : true,
     "PR_ExplodeTime" : true,
     "PR_ErrorInstallTable" : true,
     "PR_SetThreadPrivate" : true,
     "JSObject* js::GetWeakmapKeyDelegate(JSObject*)" : true, // FIXME: mark with AutoSuppressGCAnalysis instead
     "uint8 NS_IsMainThread()" : true,
 
     // Has an indirect call under it by the name "__f", which seemed too
     // generic to ignore by itself.
@@ -187,17 +175,16 @@ var ignoreFunctions = {
 
     // FIXME!
     "NS_DebugBreak": true,
 
     // These are a little overzealous -- these destructors *can* GC if they end
     // up wrapping a pending exception. See bug 898815 for the heavyweight fix.
     "void js::AutoCompartment::~AutoCompartment(int32)" : true,
     "void JSAutoCompartment::~JSAutoCompartment(int32)" : true,
-    "void js::AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM()" : true,
 
     // Bug 948646 - the only thing AutoJSContext's constructor calls
     // is an Init() routine whose entire body is covered with an
     // AutoSuppressGCAnalysis. AutoSafeJSContext is the same thing, just with
     // a different value for the 'aSafe' parameter.
     "void mozilla::AutoJSContext::AutoJSContext(mozilla::detail::GuardObjectNotifier*)" : true,
     "void mozilla::AutoSafeJSContext::~AutoSafeJSContext(int32)" : true,
 
@@ -220,23 +207,16 @@ var ignoreFunctions = {
     // code we fall back to only the dynamic checks.
     "void test::RingbufferDumper::OnTestPartResult(testing::TestPartResult*)" : true,
 
     "float64 JS_GetCurrentEmbedderTime()" : true,
 
     "uint64 js::TenuringTracer::moveObjectToTenured(JSObject*, JSObject*, int32)" : true,
     "uint32 js::TenuringTracer::moveObjectToTenured(JSObject*, JSObject*, int32)" : true,
     "void js::Nursery::freeMallocedBuffers()" : true,
-
-    // It would be cool to somehow annotate that nsTHashtable<T> will use
-    // nsTHashtable<T>::s_MatchEntry for its matchEntry function pointer, but
-    // there is no mechanism for that. So we will just annotate a particularly
-    // troublesome logging-related usage.
-    "EntryType* nsTHashtable<EntryType>::PutEntry(nsTHashtable<EntryType>::KeyType) [with EntryType = nsBaseHashtableET<nsCharPtrHashKey, nsAutoPtr<mozilla::LogModule> >; nsTHashtable<EntryType>::KeyType = const char*]" : true,
-    "EntryType* nsTHashtable<EntryType>::GetEntry(nsTHashtable<EntryType>::KeyType) const [with EntryType = nsBaseHashtableET<nsCharPtrHashKey, nsAutoPtr<mozilla::LogModule> >; nsTHashtable<EntryType>::KeyType = const char*]" : true,
 };
 
 function isProtobuf(name)
 {
     return name.match(/\bgoogle::protobuf\b/) ||
            name.match(/\bmozilla::devtools::protobuf\b/);
 }
 
@@ -328,41 +308,26 @@ function isRootedTypeName(name)
 }
 
 function isUnsafeStorage(typeName)
 {
     typeName = stripUCSAndNamespace(typeName);
     return typeName.startsWith('UniquePtr<');
 }
 
-function isSuppressConstructor(edgeType, varName)
+function isSuppressConstructor(varName)
 {
-    // Check whether this could be a constructor
-    if (edgeType.Kind != 'Function')
-        return false;
-    if (!('TypeFunctionCSU' in edgeType))
-        return false;
-    if (edgeType.Type.Kind != 'Void')
-        return false;
-
-    // Check whether the type is a known suppression type.
-    var type = edgeType.TypeFunctionCSU.Type.Name;
-    if (GCSuppressionTypes.indexOf(type) == -1)
-        return false;
-
-    // And now make sure this is the constructor, not some other method on a
-    // suppression type. varName[0] contains the qualified name.
-    var [ mangled, unmangled ] = splitFunction(varName[0]);
-    if (mangled.search(/C\dE/) == -1)
-        return false; // Mangled names of constructors have C<num>E
-    var m = unmangled.match(/([~\w]+)(?:<.*>)?\(/);
-    if (!m)
-        return false;
-    var type_stem = type.replace(/\w+::/g, '').replace(/\<.*\>/g, '');
-    return m[1] == type_stem;
+    // varName[1] contains the unqualified name
+    return [
+        "AutoSuppressGC",
+        "AutoAssertGCCallback",
+        "AutoEnterAnalysis",
+        "AutoSuppressGCAnalysis",
+        "AutoIgnoreRootingHazards"
+    ].indexOf(varName[1]) != -1;
 }
 
 // nsISupports subclasses' methods may be scriptable (or overridden
 // via binary XPCOM), and so may GC. But some fields just aren't going
 // to get overridden with something that can GC.
 function isOverridableField(initialCSU, csu, field)
 {
     if (csu != 'nsISupports')
--- a/js/src/devtools/rootAnalysis/build/sixgill.manifest
+++ b/js/src/devtools/rootAnalysis/build/sixgill.manifest
@@ -1,10 +1,10 @@
 [
-{
-"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
-"size" : 2631908,
-"hg_id" : "8cb9c3fb039a+ tip",
-"unpack" : true,
-"filename" : "sixgill.tar.xz",
-"algorithm" : "sha512"
-}
+   {
+      "hg_id" : "cd93f15a30ce",
+      "algorithm" : "sha512",
+      "digest" : "541eb3842ab6b91bd87223cad7a5e4387ef3e496e5b580c8047b8b586bc7eb69fecf3c9eb8c45a1e0deebb53554f0e8acedfe1b4ca64d93b6d008f3f2eb11389",
+      "filename" : "sixgill.tar.xz",
+      "size" : 2626640,
+      "unpack" : true
+   }
 ]
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -7,198 +7,179 @@ loadRelativeToScript('annotations.js');
 loadRelativeToScript('CFG.js');
 
 var theFunctionNameToFind;
 if (scriptArgs[0] == '--function') {
     theFunctionNameToFind = scriptArgs[1];
     scriptArgs = scriptArgs.slice(2);
 }
 
-var typeInfo_filename = scriptArgs[0] || "typeInfo.txt";
+var subclasses = {};
+var superclasses = {};
+var classFunctions = {};
 
-var subclasses = new Map(); // Map from csu => set of immediate subclasses
-var superclasses = new Map(); // Map from csu => set of immediate superclasses
-var classFunctions = new Map(); // Map from "csu:name" => set of full method name
+var fieldCallSeen = {};
 
-var virtualResolutionsSeen = new Set();
-
-function addEntry(map, name, entry)
+function addClassEntry(index, name, other)
 {
-    if (!map.has(name))
-        map.set(name, new Set());
-    map.get(name).add(entry);
+    if (!(name in index)) {
+        index[name] = [other];
+        return;
+    }
+
+    for (var entry of index[name]) {
+        if (entry == other)
+            return;
+    }
+
+    index[name].push(other);
 }
 
 // CSU is "Class/Struct/Union"
 function processCSU(csuName, csu)
 {
     if (!("FunctionField" in csu))
         return;
     for (var field of csu.FunctionField) {
         if (1 in field.Field) {
             var superclass = field.Field[1].Type.Name;
             var subclass = field.Field[1].FieldCSU.Type.Name;
             assert(subclass == csuName);
-            addEntry(subclasses, superclass, subclass);
-            addEntry(superclasses, subclass, superclass);
+            addClassEntry(subclasses, superclass, subclass);
+            addClassEntry(superclasses, subclass, superclass);
         }
         if ("Variable" in field) {
             // Note: not dealing with overloading correctly.
             var name = field.Variable.Name[0];
             var key = csuName + ":" + field.Field[0].Name[0];
-            addEntry(classFunctions, key, name);
+            if (!(key in classFunctions))
+                classFunctions[key] = [];
+            classFunctions[key].push(name);
         }
     }
 }
 
-// Return the nearest ancestor method definition, or all nearest definitions in
-// the case of multiple inheritance.
-function nearestAncestorMethods(csu, method)
+function findVirtualFunctions(initialCSU, field, suppressed)
 {
-    var key = csu + ":" + method;
+    var worklist = [initialCSU];
+    var functions = [];
 
-    if (classFunctions.has(key))
-        return new Set(classFunctions.get(key));
+    // Virtual call targets on subclasses of nsISupports may be incomplete,
+    // if the interface is scriptable. Just treat all indirect calls on
+    // nsISupports objects as potentially GC'ing, except AddRef/Release
+    // which should never enter the JS engine (even when calling dtors).
+    while (worklist.length) {
+        var csu = worklist.pop();
+        if (csu == "nsISupports" && (field == "AddRef" || field == "Release")) {
+            suppressed[0] = true;
+            return [];
+        }
+        if (isOverridableField(initialCSU, csu, field)) {
+            // We will still resolve the virtual function call, because it's
+            // nice to have as complete a callgraph as possible for other uses.
+            // But push a token saying that we can run arbitrary code.
+            functions.push(null);
+        }
 
-    var functions = new Set();
-    if (superclasses.has(csu)) {
-        for (var parent of superclasses.get(csu))
-            functions.update(nearestAncestorMethods(parent, method));
+        if (csu in superclasses) {
+            for (var superclass of superclasses[csu])
+                worklist.push(superclass);
+        }
+    }
+
+    worklist = [csu];
+    while (worklist.length) {
+        var csu = worklist.pop();
+        var key = csu + ":" + field;
+
+        if (key in classFunctions) {
+            for (var name of classFunctions[key])
+                functions.push(name);
+        }
+
+        if (csu in subclasses) {
+            for (var subclass of subclasses[csu])
+                worklist.push(subclass);
+        }
     }
 
     return functions;
 }
 
-// Return [ instantations, suppressed ], where instantiations is a Set of all
-// possible implementations of 'field' given static type 'initialCSU', plus
-// null if arbitrary other implementations are possible, and suppressed is true
-// if we the method is assumed to be non-GC'ing by annotation.
-function findVirtualFunctions(initialCSU, field)
-{
-    var worklist = [initialCSU];
-    var functions = new Set();
-
-    // Loop through all methods of initialCSU (by looking at all methods of ancestor csus).
-    //
-    // If field is nsISupports::AddRef or ::Release, return an empty list and a
-    // boolean that says we assert that it cannot GC.
-    //
-    // If this is a method that is annotated to be dangerous (eg, it could be
-    // overridden with an implementation that could GC), then use null as a
-    // signal value that it should be considered to GC, even though we'll also
-    // collect all of the instantiations for other purposes.
-
-    while (worklist.length) {
-        var csu = worklist.pop();
-        if (isSuppressedVirtualMethod(csu, field))
-            return [ new Set(), true ];
-        if (isOverridableField(initialCSU, csu, field)) {
-            // We will still resolve the virtual function call, because it's
-            // nice to have as complete a callgraph as possible for other uses.
-            // But push a token saying that we can run arbitrary code.
-            functions.add(null);
-        }
-
-        if (superclasses.has(csu))
-            worklist.push(...superclasses.get(csu));
-    }
-
-    // Now return a list of all the instantiations of the method named 'field'
-    // that could execute on an instance of initialCSU or a descendant class.
-
-    // Start with the class itself, or if it doesn't define the method, all
-    // nearest ancestor definitions.
-    functions.update(nearestAncestorMethods(initialCSU, field));
-
-    // Then recurse through all descendants to add in their definitions.
-    var worklist = [initialCSU];
-    while (worklist.length) {
-        var csu = worklist.pop();
-        var key = csu + ":" + field;
-
-        if (classFunctions.has(key))
-            functions.update(classFunctions.get(key));
-
-        if (subclasses.has(csu))
-            worklist.push(...subclasses.get(csu));
-    }
-
-    return [ functions, false ];
-}
-
-var memoized = new Map();
+var memoized = {};
 var memoizedCount = 0;
 
 function memo(name)
 {
-    if (!memoized.has(name)) {
-        let id = memoized.size + 1;
-        memoized.set(name, "" + id);
-        print(`#${id} ${name}`);
+    if (!(name in memoized)) {
+        memoizedCount++;
+        memoized[name] = "" + memoizedCount;
+        print("#" + memoizedCount + " " + name);
     }
-    return memoized.get(name);
+    return memoized[name];
 }
 
+var seenCallees = null;
+var seenSuppressedCallees = null;
+
 // Return a list of all callees that the given edge might be a call to. Each
 // one is represented by an object with a 'kind' field that is one of
-// ('direct', 'field', 'resolved-field', 'indirect', 'unknown'), though note
-// that 'resolved-field' is really a global record of virtual method
-// resolutions, indepedent of this particular edge.
+// ('direct', 'field', 'indirect', 'unknown').
 function getCallees(edge)
 {
     if (edge.Kind != "Call")
         return [];
 
     var callee = edge.Exp[0];
     var callees = [];
     if (callee.Kind == "Var") {
         assert(callee.Variable.Kind == "Func");
         callees.push({'kind': 'direct', 'name': callee.Variable.Name[0]});
     } else {
         assert(callee.Kind == "Drf");
         if (callee.Exp[0].Kind == "Fld") {
             var field = callee.Exp[0].Field;
             var fieldName = field.Name[0];
             var csuName = field.FieldCSU.Type.Name;
-            var functions;
+            var functions = null;
             if ("FieldInstanceFunction" in field) {
-                let suppressed;
-                [ functions, suppressed ] = findVirtualFunctions(csuName, fieldName, suppressed);
-                if (suppressed) {
+                var suppressed = [ false ];
+                functions = findVirtualFunctions(csuName, fieldName, suppressed);
+                if (suppressed[0]) {
                     // Field call known to not GC; mark it as suppressed so
                     // direct invocations will be ignored
                     callees.push({'kind': "field", 'csu': csuName, 'field': fieldName,
                                   'suppressed': true});
                 }
-            } else {
-                functions = new Set([null]); // field call
             }
-
-            // Known set of virtual call targets. Treat them as direct calls to
-            // all possible resolved types, but also record edges from this
-            // field call to each final callee. When the analysis is checking
-            // whether an edge can GC and it sees an unrooted pointer held live
-            // across this field call, it will know whether any of the direct
-            // callees can GC or not.
-            var targets = [];
-            var fullyResolved = true;
-            for (var name of functions) {
-                if (name === null) {
-                    // Unknown set of call targets, meaning either a function
-                    // pointer call ("field call") or a virtual method that can
-                    // be overridden in extensions.
-                    callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
-                    fullyResolved = false;
-                } else {
-                    callees.push({'kind': "direct", 'name': name});
-                    targets.push({'kind': "direct", 'name': name});
+            if (functions) {
+                // Known set of virtual call targets. Treat them as direct
+                // calls to all possible resolved types, but also record edges
+                // from this field call to each final callee. When the analysis
+                // is checking whether an edge can GC and it sees an unrooted
+                // pointer held live across this field call, it will know
+                // whether any of the direct callees can GC or not.
+                var targets = [];
+                var fullyResolved = true;
+                for (var name of functions) {
+                    if (name === null) {
+                        // virtual call on an nsISupports object
+                        callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
+                        fullyResolved = false;
+                    } else {
+                        callees.push({'kind': "direct", 'name': name});
+                        targets.push({'kind': "direct", 'name': name});
+                    }
                 }
+                if (fullyResolved)
+                    callees.push({'kind': "resolved-field", 'csu': csuName, 'field': fieldName, 'callees': targets});
+            } else {
+                // Unknown set of call targets. Non-virtual field call.
+                callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
             }
-            if (fullyResolved)
-                callees.push({'kind': "resolved-field", 'csu': csuName, 'field': fieldName, 'callees': targets});
         } else if (callee.Exp[0].Kind == "Var") {
             // indirect call through a variable.
             callees.push({'kind': "indirect", 'variable': callee.Exp[0].Variable.Name[0]});
         } else {
             // unknown call target.
             callees.push({'kind': "unknown"});
         }
     }
@@ -233,87 +214,82 @@ function getAnnotations(body)
 
     return all_annotations;
 }
 
 function getTags(functionName, body) {
     var tags = new Set();
     var annotations = getAnnotations(body);
     if (functionName in annotations) {
+        print("crawling through");
         for (var [ annName, annValue ] of annotations[functionName]) {
             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);
 
-    // Set of all callees that have been output so far, in order to suppress
-    // repeated callgraph edges from being recorded. Use a separate set for
-    // suppressed callees, since we don't want a suppressed edge (within one
-    // RAII scope) to prevent an unsuppressed edge from being recorded. The
-    // seen array is indexed by a boolean 'suppressed' variable.
-    var seen = [ new Set(), new Set() ];
-
     lastline = null;
     for (var edge of body.PEdge) {
         if (edge.Kind != "Call")
             continue;
-
-        // Whether this call is within the RAII scope of a GC suppression class
-        var edgeSuppressed = (edge.Index[0] in body.suppressed);
-
+        var edgeSuppressed = false;
+        var seen = seenCallees;
+        if (edge.Index[0] in body.suppressed) {
+            edgeSuppressed = true;
+            seen = seenSuppressedCallees;
+        }
         for (var callee of getCallees(edge)) {
-            var suppressed = Boolean(edgeSuppressed || callee.suppressed);
-            var prologue = suppressed ? "SUPPRESS_GC " : "";
+            var prologue = (edgeSuppressed || callee.suppressed) ? "SUPPRESS_GC " : "";
             prologue += memo(functionName) + " ";
             if (callee.kind == 'direct') {
-                if (!seen[+suppressed].has(callee.name)) {
-                    seen[+suppressed].add(callee.name);
+                if (!(callee.name in seen)) {
+                    seen[callee.name] = true;
                     printOnce("D " + prologue + memo(callee.name));
                 }
             } else if (callee.kind == 'field') {
                 var { csu, field } = callee;
                 printOnce("F " + prologue + "CLASS " + csu + " FIELD " + field);
             } else if (callee.kind == 'resolved-field') {
-                // Fully-resolved field (virtual method) call. Record the
-                // callgraph edges. Do not consider suppression, since it is
-                // local to this callsite and we are writing out a global
+                // Fully-resolved field call (usually a virtual method). Record
+                // the callgraph edges. Do not consider suppression, since it
+                // is local to this callsite and we are writing out a global
                 // record here.
                 //
                 // Any field call that does *not* have an R entry must be
                 // assumed to call anything.
                 var { csu, field, callees } = callee;
                 var fullFieldName = csu + "." + field;
-                if (!virtualResolutionsSeen.has(fullFieldName)) {
-                    virtualResolutionsSeen.add(fullFieldName);
+                if (!(fullFieldName in fieldCallSeen)) {
+                    fieldCallSeen[fullFieldName] = true;
                     for (var target of callees)
                         printOnce("R " + memo(fullFieldName) + " " + memo(target.name));
                 }
             } else if (callee.kind == 'indirect') {
                 printOnce("I " + prologue + "VARIABLE " + callee.variable);
             } else if (callee.kind == 'unknown') {
                 printOnce("I " + prologue + "VARIABLE UNKNOWN");
             } else {
                 printErr("invalid " + callee.kind + " callee");
                 debugger;
             }
         }
     }
 }
 
-GCSuppressionTypes = loadTypeInfo(typeInfo_filename)["Suppress GC"] || [];
+var callgraph = {};
 
 var xdb = xdbLibrary();
 xdb.open("src_comp.xdb");
 
 var minStream = xdb.min_data_stream();
 var maxStream = xdb.max_data_stream();
 
 for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) {
@@ -341,22 +317,24 @@ if (theFunctionNameToFind) {
     }
     minStream = maxStream = index;
 }
 
 function process(functionName, functionBodies)
 {
     for (var body of functionBodies)
         body.suppressed = [];
-
     for (var body of functionBodies) {
         for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
             pbody.suppressed[id] = true;
     }
 
+    seenCallees = {};
+    seenSuppressedCallees = {};
+
     for (var body of functionBodies)
         processBody(functionName, body);
 
     // GCC generates multiple constructors and destructors ("in-charge" and
     // "not-in-charge") to handle virtual base classes. They are normally
     // identical, and it appears that GCC does some magic to alias them to the
     // same thing. But this aliasing is not visible to the analysis. So we'll
     // add a dummy call edge from "foo" -> "foo *INTERNAL* ", since only "foo"
@@ -368,17 +346,17 @@ function process(functionName, functionB
     var markerPos = functionName.indexOf(internalMarker);
     if (markerPos > 0) {
         var inChargeXTor = functionName.replace(internalMarker, "");
         print("D " + memo(inChargeXTor) + " " + memo(functionName));
 
         // Bug 1056410: Oh joy. GCC does something even funkier internally,
         // where it generates calls to ~Foo() but a body for ~Foo(int32) even
         // though it uses the same mangled name for both. So we need to add a
-        // synthetic edge from ~Foo() -> ~Foo(int32).
+        // synthetic edge from the former to the latter.
         //
         // inChargeXTor will have the (int32).
         if (functionName.indexOf("::~") > 0) {
             var calledDestructor = inChargeXTor.replace("(int32)", "()");
             print("D " + memo(calledDestructor) + " " + memo(inChargeXTor));
         }
     }
 
@@ -386,47 +364,41 @@ function process(functionName, functionB
     // different kinds of constructors/destructors are:
     // C1	# complete object constructor
     // C2	# base object constructor
     // C3	# complete object allocating constructor
     // D0	# deleting destructor
     // D1	# complete object destructor
     // D2	# base object destructor
     //
-    // In actual practice, I have observed C4 and D4 xtors generated by gcc
+    // In actual practice, I have observed a C4 constructor generated by gcc
     // 4.9.3 (but not 4.7.3). The gcc source code says:
     //
     //   /* This is the old-style "[unified]" constructor.
     //      In some cases, we may emit this function and call
     //      it from the clones in order to share code and save space.  */
     //
     // Unfortunately, that "call... from the clones" does not seem to appear in
-    // the CFG we get from GCC. So if we see a C4 constructor or D4 destructor,
-    // inject an edge to it from C1, C2, and C3 (or D1, D2, and D3). (Note that
-    // C3 isn't even used in current GCC, but add the edge anyway just in
-    // case.)
-    if (functionName.indexOf("C4E") != -1 || functionName.indexOf("D4Ev") != -1) {
+    // the CFG we get from GCC. So if we see a C4 constructor, inject an edge
+    // to it from C1, C2, and C3. (Note that C3 isn't even used in current GCC,
+    // but add the edge anyway just in case.)
+    if (functionName.indexOf("C4E") != -1) {
         var [ mangled, unmangled ] = splitFunction(functionName);
         // E terminates the method name (and precedes the method parameters).
-        // If eg "C4E" shows up in the mangled name for another reason, this
-        // will create bogus edges in the callgraph. But will affect little and
-        // is somewhat difficult to avoid, so we will live with it.
-        for (let [synthetic, variant] of [['C4E', 'C1E'],
-                                          ['C4E', 'C2E'],
-                                          ['C4E', 'C3E'],
-                                          ['D4Ev', 'D1Ev'],
-                                          ['D4Ev', 'D2Ev'],
-                                          ['D4Ev', 'D3Ev']])
-        {
-            if (mangled.indexOf(synthetic) == -1)
-                continue;
-
-            let variant_mangled = mangled.replace(synthetic, variant);
-            let variant_full = variant_mangled + "$" + unmangled;
-            print("D " + memo(variant_full) + " " + memo(functionName));
+        if (mangled.indexOf("C4E") != -1) {
+            // If "C4E" shows up in the mangled name for another reason, this
+            // will create bogus edges in the callgraph. But that shouldn't
+            // matter too much, and is somewhat difficult to avoid, so we will
+            // live with it.
+            var C1 = mangled.replace("C4E", "C1E");
+            var C2 = mangled.replace("C4E", "C2E");
+            var C3 = mangled.replace("C4E", "C3E");
+            print("D " + memo(C1) + " " + memo(mangled));
+            print("D " + memo(C2) + " " + memo(mangled));
+            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()));
--- a/js/src/devtools/rootAnalysis/computeGCFunctions.js
+++ b/js/src/devtools/rootAnalysis/computeGCFunctions.js
@@ -16,30 +16,26 @@ var gcFunctions_filename = scriptArgs[1]
 var gcFunctionsList_filename = scriptArgs[2] || "gcFunctions.lst";
 var gcEdges_filename = scriptArgs[3] || "gcEdges.txt";
 var suppressedFunctionsList_filename = scriptArgs[4] || "suppressedFunctions.lst";
 
 loadCallgraph(callgraph_filename);
 
 printErr("Writing " + gcFunctions_filename);
 redirect(gcFunctions_filename);
-
 for (var name in gcFunctions) {
-    for (let readable of readableNames[name]) {
-        print("");
-        print("GC Function: " + name + "$" + readable);
-        let current = name;
-        do {
-            current = gcFunctions[current];
-            if (current in readableNames)
-                print("    " + readableNames[current][0]);
-            else
-                print("    " + current);
-        } while (current in gcFunctions);
-    }
+    print("");
+    print("GC Function: " + name + "$" + readableNames[name][0]);
+    do {
+        name = gcFunctions[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) {
     for (var readable of readableNames[name])
         print(name + "$" + readable);
 }
--- a/js/src/devtools/rootAnalysis/computeGCTypes.js
+++ b/js/src/devtools/rootAnalysis/computeGCTypes.js
@@ -1,29 +1,23 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
 
-var gcTypes_filename = scriptArgs[0] || "gcTypes.txt";
-var typeInfo_filename = scriptArgs[1] || "typeInfo.txt";
-
 var annotations = {
     'GCPointers': [],
     'GCThings': [],
     'NonGCTypes': {}, // unused
     'NonGCPointers': {},
     'RootedPointers': {},
-    'GCSuppressors': {},
 };
 
-var gDescriptors = new Map; // Map from descriptor string => Set of typeName
-
 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;
 
@@ -65,18 +59,16 @@ function processCSU(csu, body)
         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;
-        else if (tag == 'Suppress GC')
-            annotations.GCSuppressors[csu] = true;
     }
 }
 
 // csu.field is of type inner
 function addNestedStructure(csu, inner, field)
 {
     if (!(inner in structureParents))
         structureParents[inner] = [];
@@ -210,36 +202,16 @@ function addGCType(typeName, child, why,
     markGCType(typeName, '<annotation>', '(annotation)', 0, 0, "");
 }
 
 function addGCPointer(typeName)
 {
     markGCType(typeName, '<pointer-annotation>', '(annotation)', 1, 0, "");
 }
 
-// Add an arbitrary descriptor to a type, and apply it recursively to all base
-// structs and structs that contain the given typeName as a field.
-function addDescriptor(typeName, descriptor)
-{
-    if (!gDescriptors.has(descriptor))
-        gDescriptors.set(descriptor, new Set);
-    let descriptorTypes = gDescriptors.get(descriptor);
-    if (!descriptorTypes.has(typeName)) {
-        descriptorTypes.add(typeName);
-        if (typeName in structureParents) {
-            for (let [holder, field] of structureParents[typeName])
-                addDescriptor(holder, descriptor);
-        }
-        if (typeName in baseClasses) {
-            for (let base of baseClasses[typeName])
-                addDescriptor(base, descriptor);
-        }
-    }
-}
-
 for (var type of listNonGCPointers())
     annotations.NonGCPointers[type] = true;
 
 function explain(csu, indent, seen) {
     if (!seen)
         seen = new Set();
     seen.add(csu);
     if (!gcFields.has(csu))
@@ -269,31 +241,16 @@ function explain(csu, indent, seen) {
         }
         msg += child;
         print(msg);
         if (!seen.has(child))
             explain(child, indent + "  ", seen);
     }
 }
 
-var origOut = os.file.redirect(gcTypes_filename);
-
 for (var csu in gcTypes) {
     print("GCThing: " + csu);
     explain(csu, "  ");
 }
 for (var csu in gcPointers) {
     print("GCPointer: " + csu);
     explain(csu, "  ");
 }
-
-// Redirect output to the typeInfo file and close the gcTypes file.
-os.file.close(os.file.redirect(typeInfo_filename));
-
-for (let csu in annotations.GCSuppressors)
-    addDescriptor(csu, 'Suppress GC');
-
-for (let [descriptor, types] of gDescriptors) {
-    for (let csu of types)
-        print(descriptor + "$$" + csu);
-}
-
-os.file.close(os.file.redirect(origOut));
--- a/js/src/devtools/rootAnalysis/explain.py
+++ b/js/src/devtools/rootAnalysis/explain.py
@@ -1,15 +1,13 @@
 #!/usr/bin/python
 
 import re
 import argparse
 
-from collections import defaultdict
-
 parser = argparse.ArgumentParser(description='Process some integers.')
 parser.add_argument('rootingHazards', nargs='?', default='rootingHazards.txt')
 parser.add_argument('gcFunctions', nargs='?', default='gcFunctions.txt')
 parser.add_argument('hazards', nargs='?', default='hazards.txt')
 parser.add_argument('extra', nargs='?', default='unnecessary.txt')
 parser.add_argument('refs', nargs='?', default='refs.txt')
 args = parser.parse_args()
 
@@ -19,17 +17,17 @@ try:
     with open(args.rootingHazards) as rootingHazards, \
         open(args.hazards, 'w') as hazards, \
         open(args.extra, 'w') as extra, \
         open(args.refs, 'w') as refs:
         current_gcFunction = None
 
         # Map from a GC function name to the list of hazards resulting from
         # that GC function
-        hazardousGCFunctions = defaultdict(list)
+        hazardousGCFunctions = {}
 
         # List of tuples (gcFunction, index of hazard) used to maintain the
         # ordering of the hazards
         hazardOrder = []
 
         for line in rootingHazards:
             m = re.match(r'^Time: (.*)', line)
             mm = re.match(r'^Run on:', line)
@@ -50,17 +48,17 @@ try:
                 print >>refs, line
                 continue
 
             m = re.match(r"^Function.*has unrooted.*of type.*live across GC call ('?)(.*?)('?) at \S+:\d+$", line)
             if m:
                 # Function names are surrounded by single quotes. Field calls
                 # are unquoted.
                 current_gcFunction = m.group(2)
-                hazardousGCFunctions[current_gcFunction].append(line)
+                hazardousGCFunctions.setdefault(current_gcFunction, []).append(line)
                 hazardOrder.append((current_gcFunction, len(hazardousGCFunctions[current_gcFunction]) - 1))
                 num_hazards += 1
                 continue
 
             if current_gcFunction:
                 if not line.strip():
                     # Blank line => end of this hazard
                     current_gcFunction = None
@@ -81,23 +79,22 @@ try:
                     if m.group(1) in hazardousGCFunctions:
                         current_func = m.group(1)
                         explanation = line
                 elif current_func:
                     explanation += line
             if current_func:
                 gcExplanations[current_func] = explanation
 
-        for gcFunction, index in hazardOrder:
-            gcHazards = hazardousGCFunctions[gcFunction]
-
-            if gcFunction in gcExplanations:
-                print >>hazards, (gcHazards[index] + gcExplanations[gcFunction])
-            else:
-                print >>hazards, gcHazards[index]
+            for gcFunction, index in hazardOrder:
+                gcHazards = hazardousGCFunctions[gcFunction]
+                if gcFunction in gcExplanations:
+                    print >>hazards, (gcHazards[index] + gcExplanations[gcFunction])
+                else:
+                    print >>hazards, gcHazards[index]
 
 except IOError as e:
     print 'Failed: %s' % str(e)
 
 print("Wrote %s" % args.hazards)
 print("Wrote %s" % args.extra)
 print("Wrote %s" % args.refs)
 print("Found %d hazards and %d unsafe references" % (num_hazards, num_refs))
--- a/js/src/devtools/rootAnalysis/loadCallgraph.js
+++ b/js/src/devtools/rootAnalysis/loadCallgraph.js
@@ -48,18 +48,23 @@ function addGCFunction(caller, reason)
         return true;
     }
 
     return false;
 }
 
 function addCallEdge(caller, callee, suppressed)
 {
-    addToKeyedList(calleeGraph, caller, {callee:callee, suppressed:suppressed});
-    addToKeyedList(callerGraph, callee, {caller:caller, suppressed:suppressed});
+    if (!(caller in calleeGraph))
+        calleeGraph[caller] = [];
+    calleeGraph[caller].push({callee:callee, suppressed:suppressed});
+
+    if (!(callee in callerGraph))
+        callerGraph[callee] = [];
+    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 = [""];
--- a/js/src/devtools/rootAnalysis/run-test.py
+++ b/js/src/devtools/rootAnalysis/run-test.py
@@ -1,89 +1,152 @@
 #!/usr/bin/env python
 # 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/.
 
+import sys
 import os
-import site
+import re
+import json
 import subprocess
-import argparse
 
-testdir = os.path.abspath(os.path.join(os.path.dirname(__file__), 't'))
-site.addsitedir(testdir)
-from testlib import Test, equal
-
-scriptdir = os.path.abspath(os.path.dirname(__file__))
+testdir = os.path.abspath(os.path.dirname(__file__))
 
-parser = argparse.ArgumentParser(description='run hazard analysis tests')
-parser.add_argument(
-    '--js', default=os.environ.get('JS'),
-    help='JS binary to run the tests with')
-parser.add_argument(
-    '--sixgill', default=os.environ.get('SIXGILL', os.path.join(testdir, "sixgill")),
-    help='Path to root of sixgill installation')
-parser.add_argument(
-    '--sixgill-bin', default=os.environ.get('SIXGILL_BIN'),
-    help='Path to sixgill binary dir')
-parser.add_argument(
-    '--sixgill-plugin', default=os.environ.get('SIXGILL_PLUGIN'),
-    help='Full path to sixgill gcc plugin')
-parser.add_argument(
-    '--gccdir', default=os.environ.get('GCCDIR'),
-    help='Path to GCC installation dir')
-parser.add_argument(
-    '--cc', default=os.environ.get('CC'),
-    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'],
-    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:
-        cfg.cc = os.path.join(cfg.gccdir, "bin", "gcc")
-    else:
-        cfg.cc = "gcc"
-if not cfg.cxx:
-    if cfg.gccdir:
-        cfg.cxx = os.path.join(cfg.gccdir, "bin", "g++")
-    else:
-        cfg.cxx = "g++"
-if not cfg.sixgill_bin:
-    cfg.sixgill_bin = os.path.join(cfg.sixgill, "usr", "bin")
-if not cfg.sixgill_plugin:
-    cfg.sixgill_plugin = os.path.join(cfg.sixgill, "usr", "libexec", "sixgill", "gcc", "xgill.so")
-
-subprocess.check_call([cfg.js, '-e', 'if (!getBuildConfiguration()["has-ctypes"]) quit(1)'])
+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)
+    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'] }
 
-try:
-    os.mkdir(os.path.join('t', 'out'))
-except OSError:
-    pass
+        # 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)
 
-for name in cfg.tests:
-    name = os.path.basename(name)
+    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, 'out', name)
-    try:
+    outdir = os.path.join(testdir, "test-output", name)
+    if not os.path.exists(outdir):
         os.mkdir(outdir)
-    except OSError:
-        pass
+
+    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)
 
-    test = Test(indir, outdir, cfg)
+    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"), {'test': test, 'equal': equal})
+    execfile(os.path.join(indir, "test.py"))
     print("TEST-PASSED: %s" % name)
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/exceptions/source.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-#define ANNOTATE(property) __attribute__((tag(property)))
-
-struct Cell { int f; } ANNOTATE("GC Thing");
-
-extern void GC() ANNOTATE("GC Call");
-
-void GC()
-{
-    // If the implementation is too trivial, the function body won't be emitted at all.
-    asm("");
-}
-
-class RAII_GC {
-  public:
-    RAII_GC() {}
-    ~RAII_GC() { GC(); }
-};
-
-// ~AutoSomething calls GC because of the RAII_GC field. The constructor,
-// though, should *not* GC -- unless it throws an exception. Which is not
-// possible when compiled with -fno-exceptions.
-class AutoSomething {
-    RAII_GC gc;
-  public:
-    AutoSomething() : gc() {
-        asm(""); // Ooh, scary, this might throw an exception
-    }
-    ~AutoSomething() {
-        asm("");
-    }
-};
-
-extern void usevar(Cell* cell);
-
-void f() {
-    Cell* thing = nullptr; // Live range starts here
-
-    {
-        AutoSomething smth; // Constructor can GC only if exceptions are enabled
-        usevar(thing); // Live range ends here
-    } // In particular, 'thing' is dead at the destructor, so no hazard
-}
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/exceptions/test.py
+++ /dev/null
@@ -1,19 +0,0 @@
-test.compile("source.cpp", '-fno-exceptions')
-test.run_analysis_script('gcTypes')
-
-hazards = test.load_hazards()
-assert(len(hazards) == 0)
-
-# If we compile with exceptions, then there *should* be a hazard because
-# AutoSomething::AutoSomething might throw an exception, which would cause the
-# partially-constructed value to be torn down, which will call ~RAII_GC.
-
-test.compile("source.cpp", '-fexceptions')
-test.run_analysis_script('gcTypes')
-
-hazards = test.load_hazards()
-assert(len(hazards) == 1)
-hazard = hazards[0]
-assert(hazard.function == 'void f()')
-assert(hazard.variable == 'thing')
-assert("AutoSomething::AutoSomething" in hazard.GCFunction)
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/hazards/source.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-#define ANNOTATE(property) __attribute__((tag(property)))
-
-struct Cell { int f; } ANNOTATE("GC Thing");
-
-class AutoSuppressGC_Base {
-  public:
-    AutoSuppressGC_Base() {}
-    ~AutoSuppressGC_Base() {}
-} ANNOTATE("Suppress GC");
-
-class AutoSuppressGC_Child : public AutoSuppressGC_Base {
-  public:
-    AutoSuppressGC_Child() : AutoSuppressGC_Base() {}
-};
-
-class AutoSuppressGC {
-    AutoSuppressGC_Child helpImBeingSuppressed;
-
-  public:
-    AutoSuppressGC() {}
-};
-
-extern void GC() ANNOTATE("GC Call");
-extern void invisible();
-
-void GC()
-{
-    // If the implementation is too trivial, the function body won't be emitted at all.
-    asm("");
-    invisible();
-}
-
-extern void foo(Cell*);
-
-void suppressedFunction() {
-    GC(); // Calls GC, but is always called within AutoSuppressGC
-}
-
-void halfSuppressedFunction() {
-    GC(); // Calls GC, but is sometimes called within AutoSuppressGC
-}
-
-void unsuppressedFunction() {
-    GC(); // Calls GC, never within AutoSuppressGC
-}
-
-volatile static int x = 3;
-volatile static int* xp = &x;
-struct GCInDestructor {
-    ~GCInDestructor() {
-        invisible();
-        asm("");
-        *xp = 4;
-        GC();
-    }
-};
-
-Cell*
-f()
-{
-    GCInDestructor kaboom;
-
-    Cell cell;
-    Cell* cell1 = &cell;
-    Cell* cell2 = &cell;
-    Cell* cell3 = &cell;
-    Cell* cell4 = &cell;
-    {
-        AutoSuppressGC nogc;
-        suppressedFunction();
-        halfSuppressedFunction();
-    }
-    foo(cell1);
-    halfSuppressedFunction();
-    foo(cell2);
-    unsuppressedFunction();
-    {
-        // Old bug: it would look from the first AutoSuppressGC constructor it
-        // found to the last destructor. This statement *should* have no effect.
-        AutoSuppressGC nogc;
-    }
-    foo(cell3);
-    Cell* cell5 = &cell;
-    foo(cell5);
-
-    // Hazard in return value due to ~GCInDestructor
-    Cell* cell6 = &cell;
-    return cell6;
-}
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/hazards/test.py
+++ /dev/null
@@ -1,36 +0,0 @@
-test.compile("source.cpp")
-test.run_analysis_script('gcTypes')
-
-# gcFunctions should be the inverse, but we get to rely on unmangled names here.
-gcFunctions = test.load_gcFunctions()
-print(gcFunctions)
-assert('void GC()' in gcFunctions)
-assert('void suppressedFunction()' not in gcFunctions)
-assert('void halfSuppressedFunction()' in gcFunctions)
-assert('void unsuppressedFunction()' in gcFunctions)
-assert('Cell* f()' in gcFunctions)
-
-hazards = test.load_hazards()
-hazmap = {haz.variable: haz for haz in hazards}
-assert('cell1' not in hazmap)
-assert('cell2' in hazmap)
-assert('cell3' in hazmap)
-assert('cell4' not in hazmap)
-assert('cell5' not in hazmap)
-assert('cell6' not in hazmap)
-assert('<returnvalue>' in hazmap)
-
-# All hazards should be in f()
-assert(hazmap['cell2'].function == 'Cell* f()')
-assert(len(set(haz.function for haz in hazards)) == 1)
-
-# Check that the correct GC call is reported for each hazard. (cell3 has a
-# hazard from two different GC calls; it doesn't really matter which is
-# reported.)
-assert(hazmap['cell2'].GCFunction == 'void halfSuppressedFunction()')
-assert(hazmap['cell3'].GCFunction in ('void halfSuppressedFunction()', 'void unsuppressedFunction()'))
-assert(hazmap['<returnvalue>'].GCFunction == 'void GCInDestructor::~GCInDestructor()')
-
-# Type names are handy to have in the report.
-assert(hazmap['cell2'].type == 'Cell*')
-assert(hazmap['<returnvalue>'].type == 'Cell*')
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-#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;
-}
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/sixgill-tree/test.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import re
-
-test.compile("source.cpp")
-test.computeGCTypes()
-body = test.process_body(test.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 = test.process_body(test.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 = test.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 = test.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 = test.load_gcTypes()
-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'])
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/sixgill.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python
-# 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/.
-
-from collections import defaultdict
-
-# 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].split("$")[-1]
-        loc = body['Location']
-        self['LineRange'] = (loc[0]['Line'], loc[1]['Line'])
-        self['Filename'] = loc[0]['CacheString']
-        self['Edges'] = body.get('PEdge', [])
-        self['Points'] = { i: p['Location']['Line'] for i, p in enumerate(body['PPoint'], 1) }
-        self['Index'] = body['Index']
-        self['Variables'] = { x['Variable']['Name'][0].split("$")[-1]: x['Type'] for x in body['DefineVariable'] }
-
-        # Indexes
-        self['Line2Points'] = defaultdict(list)
-        for point, line in self['Points'].items():
-            self['Line2Points'][line].append(point)
-        self['SrcPoint2Edges'] = defaultdict(list)
-        for edge in self['Edges']:
-            src, dst = edge['Index']
-            self['SrcPoint2Edges'][src].append(edge)
-        self['Line2Edges'] = defaultdict(list)
-        for (src, edges) in self['SrcPoint2Edges'].items():
-            line = self['Points'][src]
-            self['Line2Edges'][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)]
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/suppression/source.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-#define ANNOTATE(property) __attribute__((tag(property)))
-
-struct Cell { int f; } ANNOTATE("GC Thing");
-
-class AutoSuppressGC_Base {
-  public:
-    AutoSuppressGC_Base() {}
-    ~AutoSuppressGC_Base() {}
-} ANNOTATE("Suppress GC");
-
-class AutoSuppressGC_Child : public AutoSuppressGC_Base {
-  public:
-    AutoSuppressGC_Child() : AutoSuppressGC_Base() {}
-};
-
-class AutoSuppressGC {
-    AutoSuppressGC_Child helpImBeingSuppressed;
-
-  public:
-    AutoSuppressGC() {}
-};
-
-extern void GC() ANNOTATE("GC Call");
-
-void GC()
-{
-    // If the implementation is too trivial, the function body won't be emitted at all.
-    asm("");
-}
-
-extern void foo(Cell*);
-
-void suppressedFunction() {
-    GC(); // Calls GC, but is always called within AutoSuppressGC
-}
-
-void halfSuppressedFunction() {
-    GC(); // Calls GC, but is sometimes called within AutoSuppressGC
-}
-
-void unsuppressedFunction() {
-    GC(); // Calls GC, never within AutoSuppressGC
-}
-
-void f() {
-    Cell* cell1 = nullptr;
-    Cell* cell2 = nullptr;
-    Cell* cell3 = nullptr;
-    {
-        AutoSuppressGC nogc;
-        suppressedFunction();
-        halfSuppressedFunction();
-    }
-    foo(cell1);
-    halfSuppressedFunction();
-    foo(cell2);
-    unsuppressedFunction();
-    {
-        // Old bug: it would look from the first AutoSuppressGC constructor it
-        // found to the last destructor. This statement *should* have no effect.
-        AutoSuppressGC nogc;
-    }
-    foo(cell3);
-}
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/suppression/test.py
+++ /dev/null
@@ -1,23 +0,0 @@
-test.compile("source.cpp")
-test.run_analysis_script('gcTypes', upto='gcFunctions')
-
-# 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()
-
-# Only one of these is fully suppressed (ie, *always* called within the scope
-# of an AutoSuppressGC).
-assert(len(filter(lambda f: 'suppressedFunction' in f, suppressed)) == 1)
-assert(len(filter(lambda f: 'halfSuppressedFunction' in f, suppressed)) == 0)
-assert(len(filter(lambda f: 'unsuppressedFunction' in f, suppressed)) == 0)
-
-# gcFunctions should be the inverse, but we get to rely on unmangled names here.
-gcFunctions = test.load_gcFunctions()
-print(gcFunctions)
-assert('void GC()' in gcFunctions)
-assert('void suppressedFunction()' not in gcFunctions)
-assert('void halfSuppressedFunction()' in gcFunctions)
-assert('void unsuppressedFunction()' in gcFunctions)
-assert('void f()' in gcFunctions)
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/t/testlib.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import json
-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'])
-
-
-def equal(got, expected):
-    if got != expected:
-        print("Got '%s', expected '%s'" % (got, expected))
-
-def extract_unmangled(func):
-    return func.split('$')[-1]
-
-
-class Test(object):
-    def __init__(self, indir, outdir, cfg):
-        self.indir = indir
-        self.outdir = outdir
-        self.cfg = cfg
-
-    def infile(self, path):
-        return os.path.join(self.indir, path)
-
-    def binpath(self, prog):
-        return os.path.join(self.cfg.sixgill_bin, prog)
-
-    def compile(self, source, options = ''):
-        cmd = "{CXX} -c {source} -O3 -std=c++11 -fplugin={sixgill} -fplugin-arg-xgill-mangle=1 {options}".format(
-            source=self.infile(source),
-            CXX=self.cfg.cxx, sixgill=self.cfg.sixgill_plugin,
-            options=options)
-        if self.cfg.verbose:
-            print("Running %s" % cmd)
-        subprocess.check_call(["sh", "-c", cmd])
-
-    def load_db_entry(self, dbname, pattern):
-        '''Look up an entry from an XDB database file, 'pattern' may be an exact
-        matching string, or an re pattern object matching a single entry.'''
-
-        if not isinstance(pattern, basestring):
-            output = subprocess.check_output([self.binpath("xdbkeys"), dbname + ".xdb"])
-            matches = filter(lambda _: re.search(pattern, _), output.splitlines())
-            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([self.binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
-        return json.loads(output)
-
-    def run_analysis_script(self, phase, upto=None):
-        file("defaults.py", "w").write('''\
-analysis_scriptdir = '{scriptdir}'
-sixgill_bin = '{bindir}'
-'''.format(scriptdir=scriptdir, bindir=self.cfg.sixgill_bin))
-        cmd = [os.path.join(scriptdir, "analyze.py"), phase]
-        if upto:
-            cmd += ["--upto", upto]
-        cmd.append("--source=%s" % self.indir)
-        cmd.append("--objdir=%s" % self.outdir)
-        cmd.append("--js=%s" % self.cfg.js)
-        if self.cfg.verbose:
-            cmd.append("--verbose")
-            print("Running " + " ".join(cmd))
-        subprocess.check_call(cmd)
-
-    def computeGCTypes(self):
-        self.run_analysis_script("gcTypes", upto="gcTypes")
-
-    def computeHazards(self):
-        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 file(fullpath))
-        return filter(lambda _: _ is not None, values)
-
-    def load_suppressed_functions(self):
-        return set(self.load_text_file("suppressedFunctions.lst"))
-
-    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_hazards(self):
-        def grab_hazard(line):
-            m = re.match(r"Function '(.*?)' has unrooted '(.*?)' of type '(.*?)' live across GC call '(.*?)' at (.*)", line)
-            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)
-
-    def process_body(self, body):
-        return Body(body)
-
-    def process_bodies(self, bodies):
-        return [self.process_body(b) for b in bodies]
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/test/source.cpp
@@ -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;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/test/test.py
@@ -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'])
--- a/js/src/devtools/rootAnalysis/utility.js
+++ b/js/src/devtools/rootAnalysis/utility.js
@@ -1,25 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 
 "use strict";
 
 // gcc appends this to mangled function names for "not in charge"
 // constructors/destructors.
 var internalMarker = " *INTERNAL* ";
 
-if (! Set.prototype.hasOwnProperty("update")) {
-    Object.defineProperty(Set.prototype, "update", {
-        value: function (collection) {
-            for (let elt of collection)
-                this.add(elt);
-        }
-    });
-}
-
 function assert(x, msg)
 {
     if (x)
         return;
     debugger;
     if (msg)
         throw "assertion failed: " + msg + "\n" + (Error().stack);
     else
@@ -186,26 +177,8 @@ function* readFileLines_gen(filename)
     if (fp.isNull())
         throw "Unable to open '" + filename + "'"
 
     while (libc.getline(linebuf.address(), bufsize.address(), fp) > 0)
         yield linebuf.readString();
     libc.fclose(fp);
     libc.free(ctypes.void_t.ptr(linebuf));
 }
-
-function addToKeyedList(collection, key, entry)
-{
-    if (!(key in collection))
-        collection[key] = [];
-    collection[key].push(entry);
-}
-
-function loadTypeInfo(filename)
-{
-    var info = {};
-    for (var line of readFileLines_gen(filename)) {
-        line = line.replace(/\n/, "");
-        let [property, name] = line.split("$$");
-        addToKeyedList(info, property, name);
-    }
-    return info;
-}
--- a/js/src/gc/Iteration.cpp
+++ b/js/src/gc/Iteration.cpp
@@ -90,44 +90,46 @@ js::IterateChunks(JSRuntime* rt, void* d
         chunkCallback(rt, data, chunk);
 }
 
 void
 js::IterateScripts(JSRuntime* rt, JSCompartment* compartment,
                    void* data, IterateScriptCallback scriptCallback)
 {
     MOZ_ASSERT(!rt->mainThread.suppressGC);
-    AutoEmptyNursery empty(rt);
+    rt->gc.evictNursery();
+    MOZ_ASSERT(rt->gc.nursery.isEmpty());
+
     AutoPrepareForTracing prep(rt, SkipAtoms);
 
     if (compartment) {
-        Zone* zone = compartment->zone();
-        for (auto script = zone->cellIter<JSScript>(empty); !script.done(); script.next()) {
+        for (ZoneCellIterUnderGC i(compartment->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             if (script->compartment() == compartment)
                 scriptCallback(rt, data, script);
         }
     } else {
         for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-            for (auto script = zone->cellIter<JSScript>(empty); !script.done(); script.next())
-                scriptCallback(rt, data, script);
+            for (ZoneCellIterUnderGC i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next())
+                scriptCallback(rt, data, i.get<JSScript>());
         }
     }
 }
 
 void
 js::IterateGrayObjects(Zone* zone, GCThingCallback cellCallback, void* data)
 {
-    JSRuntime* rt = zone->runtimeFromMainThread();
-    AutoEmptyNursery empty(rt);
-    AutoPrepareForTracing prep(rt, SkipAtoms);
+    zone->runtimeFromMainThread()->gc.evictNursery();
+    AutoPrepareForTracing prep(zone->runtimeFromMainThread(), SkipAtoms);
 
     for (auto thingKind : ObjectAllocKinds()) {
-        for (auto obj = zone->cellIter<JSObject>(thingKind, empty); !obj.done(); obj.next()) {
+        for (ZoneCellIterUnderGC i(zone, thingKind); !i.done(); i.next()) {
+            JSObject* obj = i.get<JSObject>();
             if (obj->asTenured().isMarked(GRAY))
-                cellCallback(data, JS::GCCellPtr(obj.get()));
+                cellCallback(data, JS::GCCellPtr(obj));
         }
     }
 }
 
 JS_PUBLIC_API(void)
 JS_IterateCompartments(JSRuntime* rt, void* data,
                        JSIterateCompartmentCallback compartmentCallback)
 {
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -756,17 +756,17 @@ Statistics::Statistics(JSRuntime* rt)
     fp(nullptr),
     gcDepth(0),
     nonincrementalReason_(nullptr),
     timedGCStart(0),
     preBytes(0),
     maxPauseInInterval(0),
     phaseNestingDepth(0),
     activeDagSlot(PHASE_DAG_NONE),
-    suspended(0),
+    suspendedPhaseNestingDepth(0),
     sliceCallback(nullptr),
     nurseryCollectionCallback(nullptr),
     aborted(false)
 {
     PodArrayZero(phaseTotals);
     PodArrayZero(counts);
     PodArrayZero(phaseStartTimes);
     for (auto d : MakeRange(NumTimingArrays))
@@ -1064,17 +1064,17 @@ Statistics::startTimingMutator()
 {
     if (phaseNestingDepth != 0) {
         // Should only be called from outside of GC.
         MOZ_ASSERT(phaseNestingDepth == 1);
         MOZ_ASSERT(phaseNesting[0] == PHASE_MUTATOR);
         return false;
     }
 
-    MOZ_ASSERT(suspended == 0);
+    MOZ_ASSERT(suspendedPhaseNestingDepth == 0);
 
     timedGCTime = 0;
     phaseStartTimes[PHASE_MUTATOR] = 0;
     phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR] = 0;
     timedGCStart = 0;
 
     beginPhase(PHASE_MUTATOR);
     return true;
@@ -1090,58 +1090,29 @@ Statistics::stopTimingMutator(double& mu
     endPhase(PHASE_MUTATOR);
     mutator_ms = t(phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR]);
     gc_ms = t(timedGCTime);
 
     return true;
 }
 
 void
-Statistics::suspendPhases(Phase suspension)
-{
-    MOZ_ASSERT(suspension == PHASE_EXPLICIT_SUSPENSION || suspension == PHASE_IMPLICIT_SUSPENSION);
-    while (phaseNestingDepth) {
-        MOZ_ASSERT(suspended < mozilla::ArrayLength(suspendedPhases));
-        Phase parent = phaseNesting[phaseNestingDepth - 1];
-        suspendedPhases[suspended++] = parent;
-        recordPhaseEnd(parent);
-    }
-    suspendedPhases[suspended++] = suspension;
-}
-
-void
-Statistics::resumePhases()
-{
-#ifdef DEBUG
-    Phase popped = suspendedPhases[--suspended];
-    MOZ_ASSERT(popped == PHASE_EXPLICIT_SUSPENSION || popped == PHASE_IMPLICIT_SUSPENSION);
-#endif
-    while (suspended &&
-           suspendedPhases[suspended - 1] != PHASE_EXPLICIT_SUSPENSION &&
-           suspendedPhases[suspended - 1] != PHASE_IMPLICIT_SUSPENSION)
-    {
-        Phase resumePhase = suspendedPhases[--suspended];
-        if (resumePhase == PHASE_MUTATOR)
-            timedGCTime += PRMJ_Now() - timedGCStart;
-        beginPhase(resumePhase);
-    }
-}
-
-void
 Statistics::beginPhase(Phase phase)
 {
     Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
 
     // Re-entry is allowed during callbacks, so pause callback phases while
     // other phases are in progress, auto-resuming after they end. As a result,
     // nested GC time will not be accounted against the callback phases.
     //
     // Reuse this mechanism for managing PHASE_MUTATOR.
     if (parent == PHASE_GC_BEGIN || parent == PHASE_GC_END || parent == PHASE_MUTATOR) {
-        suspendPhases(PHASE_IMPLICIT_SUSPENSION);
+        MOZ_ASSERT(suspendedPhaseNestingDepth < mozilla::ArrayLength(suspendedPhases));
+        suspendedPhases[suspendedPhaseNestingDepth++] = parent;
+        recordPhaseEnd(parent);
         parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
     }
 
     // Guard against any other re-entry.
     MOZ_ASSERT(!phaseStartTimes[phase]);
 
     MOZ_ASSERT(phases[phase].index == phase);
     MOZ_ASSERT(phaseNestingDepth < MAX_NESTING);
@@ -1178,18 +1149,22 @@ Statistics::endPhase(Phase phase)
 {
     recordPhaseEnd(phase);
 
     if (phases[phase].parent == PHASE_MULTI_PARENTS)
         activeDagSlot = PHASE_DAG_NONE;
 
     // When emptying the stack, we may need to resume a callback phase
     // (PHASE_GC_BEGIN/END) or return to timing the mutator (PHASE_MUTATOR).
-    if (phaseNestingDepth == 0 && suspended > 0 && suspendedPhases[suspended - 1] == PHASE_IMPLICIT_SUSPENSION)
-        resumePhases();
+    if (phaseNestingDepth == 0 && suspendedPhaseNestingDepth > 0) {
+        Phase resumePhase = suspendedPhases[--suspendedPhaseNestingDepth];
+        if (resumePhase == PHASE_MUTATOR)
+            timedGCTime += PRMJ_Now() - timedGCStart;
+        beginPhase(resumePhase);
+    }
 }
 
 void
 Statistics::endParallelPhase(Phase phase, const GCParallelTask* task)
 {
     phaseNestingDepth--;
 
     if (!slices.empty())
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -81,18 +81,16 @@ enum Phase : uint8_t {
     PHASE_MARK_CCWS,
     PHASE_MARK_ROOTERS,
     PHASE_MARK_RUNTIME_DATA,
     PHASE_MARK_EMBEDDING,
     PHASE_MARK_COMPARTMENTS,
 
     PHASE_LIMIT,
     PHASE_NONE = PHASE_LIMIT,
-    PHASE_EXPLICIT_SUSPENSION = PHASE_LIMIT,
-    PHASE_IMPLICIT_SUSPENSION,
     PHASE_MULTI_PARENTS
 };
 
 enum Stat {
     STAT_NEW_CHUNK,
     STAT_DESTROY_CHUNK,
     STAT_MINOR_GC,
 
@@ -166,32 +164,16 @@ struct Statistics
 
     explicit Statistics(JSRuntime* rt);
     ~Statistics();
 
     void beginPhase(Phase phase);
     void endPhase(Phase phase);
     void endParallelPhase(Phase phase, const GCParallelTask* task);
 
-    // Occasionally, we may be in the middle of something that is tracked by
-    // this class, and we need to do something unusual (eg evict the nursery)
-    // that doesn't normally nest within the current phase. Suspend the
-    // currently tracked phase stack, at which time the caller is free to do
-    // other tracked operations.
-    //
-    // This also happens internally with PHASE_GC_BEGIN and other "non-GC"
-    // phases. While in these phases, any beginPhase will automatically suspend
-    // the non-GC phase, until that inner stack is complete, at which time it
-    // will automatically resume the non-GC phase. Explicit suspensions do not
-    // get auto-resumed.
-    void suspendPhases(Phase suspension = PHASE_EXPLICIT_SUSPENSION);
-
-    // Resume a suspended stack of phases.
-    void resumePhases();
-
     void beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind,
                     SliceBudget budget, JS::gcreason::Reason reason);
     void endSlice();
     void setSliceCycleCount(unsigned cycleCount);
 
     MOZ_MUST_USE bool startTimingMutator();
     MOZ_MUST_USE bool stopTimingMutator(double& mutator_ms, double& gc_ms);
 
@@ -331,24 +313,23 @@ struct Statistics
     mutable int64_t maxPauseInInterval;
 
     /* Phases that are currently on stack. */
     Phase phaseNesting[MAX_NESTING];
     size_t phaseNestingDepth;
     size_t activeDagSlot;
 
     /*
-     * Certain phases can interrupt the phase stack, eg callback phases. When
-     * this happens, we move the suspended phases over to a sepearate list,
-     * terminated by a dummy PHASE_SUSPENSION phase (so that we can nest
-     * suspensions by suspending multiple stacks with a PHASE_SUSPENSION in
-     * between).
+     * To avoid recursive nesting, we discontinue a callback phase when any
+     * other phases are started. Remember what phase to resume when the inner
+     * phases are complete. (And because GCs can nest within the callbacks any
+     * number of times, we need a whole stack of of phases to resume.)
      */
-    Phase suspendedPhases[MAX_NESTING * 3];
-    size_t suspended;
+    Phase suspendedPhases[MAX_NESTING];
+    size_t suspendedPhaseNestingDepth;
 
     /* Sweep times for SCCs of compartments. */
     Vector<int64_t, 0, SystemAllocPolicy> sccTimes;
 
     JS::GCSliceCallback sliceCallback;
     JS::GCNurseryCollectionCallback nurseryCollectionCallback;
 
     /*
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -148,23 +148,23 @@ Zone::sweepBreakpoints(FreeOp* fop)
         return;
 
     /*
      * Sweep all compartments in a zone at the same time, since there is no way
      * to iterate over the scripts belonging to a single compartment in a zone.
      */
 
     MOZ_ASSERT(isGCSweepingOrCompacting());
-    for (auto iter = cellIter<JSScript>(); !iter.done(); iter.next()) {
-        JSScript* script = iter;
+    for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
+        JSScript* script = i.get<JSScript>();
         if (!script->hasAnyBreakpointsOrStepMode())
             continue;
 
         bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script);
-        MOZ_ASSERT(script == iter);
+        MOZ_ASSERT(script == i.get<JSScript>());
         for (unsigned i = 0; i < script->length(); i++) {
             BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i));
             if (!site)
                 continue;
 
             Breakpoint* nextbp;
             for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
                 nextbp = bp->nextInSite();
@@ -202,27 +202,30 @@ Zone::discardJitCode(FreeOp* fop)
         return;
 
     if (isPreservingCode()) {
         PurgeJITCaches(this);
     } else {
 
 #ifdef DEBUG
         /* Assert no baseline scripts are marked as active. */
-        for (auto script = cellIter<JSScript>(); !script.done(); script.next())
+        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             MOZ_ASSERT_IF(script->hasBaselineScript(), !script->baselineScript()->active());
+        }
 #endif
 
         /* Mark baseline scripts on the stack as active. */
         jit::MarkActiveBaselineScripts(this);
 
         /* Only mark OSI points if code is being discarded. */
         jit::InvalidateAll(fop, this);
 
-        for (auto script = cellIter<JSScript>(); !script.done(); script.next())  {
+        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             jit::FinishInvalidation(fop, script);
 
             /*
              * Discard baseline script if it's not marked as active. Note that
              * this also resets the active flag.
              */
             jit::FinishDiscardBaselineScript(fop, script);
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -79,19 +79,16 @@ struct UniqueIdGCPolicy {
 using UniqueIdMap = GCHashMap<Cell*,
                               uint64_t,
                               PointerHasher<Cell*, 3>,
                               SystemAllocPolicy,
                               UniqueIdGCPolicy>;
 
 extern uint64_t NextCellUniqueId(JSRuntime* rt);
 
-template <typename T>
-class ZoneCellIter;
-
 } // namespace gc
 } // namespace js
 
 namespace JS {
 
 // A zone is a collection of compartments. Every compartment belongs to exactly
 // one zone. In Firefox, there is roughly one zone per tab along with a system
 // zone for everything else. Zones mainly serve as boundaries for garbage
@@ -155,23 +152,16 @@ struct Zone : public JS::shadow::Zone,
     void updateMallocCounter(size_t nbytes) {
         // Note: this code may be run from worker threads. We tolerate any
         // thread races when updating gcMallocBytes.
         gcMallocBytes -= ptrdiff_t(nbytes);
         if (MOZ_UNLIKELY(isTooMuchMalloc()))
             onTooMuchMalloc();
     }
 
-    // Iterate over all cells in the zone. See the definition of ZoneCellIter
-    // in jsgcinlines.h for the possible arguments and documentation.
-    template <typename T, typename... Args>
-    js::gc::ZoneCellIter<T> cellIter(Args... args) {
-        return js::gc::ZoneCellIter<T>(const_cast<Zone*>(this), mozilla::Forward<Args>(args)...);
-    }
-
     bool isTooMuchMalloc() const { return gcMallocBytes <= 0; }
     void onTooMuchMalloc();
 
     MOZ_MUST_USE void* onOutOfMemory(js::AllocFunction allocFunc, size_t nbytes,
                                                void* reallocPtr = nullptr) {
         if (!js::CurrentThreadCanAccessRuntime(runtime_))
             return nullptr;
         return runtimeFromMainThread()->onOutOfMemory(allocFunc, nbytes, reallocPtr);
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -483,17 +483,16 @@ void
 BaselineScript::Trace(JSTracer* trc, BaselineScript* script)
 {
     script->trace(trc);
 }
 
 void
 BaselineScript::Destroy(FreeOp* fop, BaselineScript* script)
 {
-
     MOZ_ASSERT(!script->hasPendingIonBuilder());
 
     script->unlinkDependentWasmModules(fop);
 
     /*
      * When the script contains pointers to nursery things, the store buffer can
      * contain entries that point into the fallback stub space. Since we can
      * destroy scripts outside the context of a GC, this situation could result
@@ -1167,43 +1166,46 @@ jit::AddSizeOfBaselineData(JSScript* scr
 void
 jit::ToggleBaselineProfiling(JSRuntime* runtime, bool enable)
 {
     JitRuntime* jrt = runtime->jitRuntime();
     if (!jrt)
         return;
 
     for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) {
-        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
+        for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             if (!script->hasBaselineScript())
                 continue;
             AutoWritableJitCode awjc(script->baselineScript()->method());
             script->baselineScript()->toggleProfilerInstrumentation(enable);
         }
     }
 }
 
 #ifdef JS_TRACE_LOGGING
 void
 jit::ToggleBaselineTraceLoggerScripts(JSRuntime* runtime, bool enable)
 {
     for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) {
-        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
+        for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             if (!script->hasBaselineScript())
                 continue;
             script->baselineScript()->toggleTraceLoggerScripts(runtime, script, enable);
         }
     }
 }
 
 void
 jit::ToggleBaselineTraceLoggerEngine(JSRuntime* runtime, bool enable)
 {
     for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) {
-        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
+        for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             if (!script->hasBaselineScript())
                 continue;
             script->baselineScript()->toggleTraceLoggerEngine(enable);
         }
     }
 }
 #endif
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -6,17 +6,16 @@
 
 #include "jit/Ion.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/ThreadLocal.h"
 
 #include "jscompartment.h"
-#include "jsgc.h"
 #include "jsprf.h"
 
 #include "gc/Marking.h"
 #include "jit/AliasAnalysis.h"
 #include "jit/AlignmentMaskAnalysis.h"
 #include "jit/BacktrackingAllocator.h"
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineInspector.h"
@@ -585,18 +584,18 @@ jit::LazyLinkTopActivation(JSContext* cx
     return calleeScript->baselineOrIonRawPointer();
 }
 
 /* static */ void
 JitRuntime::Mark(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
     MOZ_ASSERT(!trc->runtime()->isHeapMinorCollecting());
     Zone* zone = trc->runtime()->atomsCompartment(lock)->zone();
-    for (auto i = zone->cellIter<JitCode>(); !i.done(); i.next()) {
-        JitCode* code = i;
+    for (gc::ZoneCellIterUnderGC i(zone, gc::AllocKind::JITCODE); !i.done(); i.next()) {
+        JitCode* code = i.get<JitCode>();
         TraceRoot(trc, &code, "wrapper");
     }
 }
 
 /* static */ void
 JitRuntime::MarkJitcodeGlobalTableUnconditionally(JSTracer* trc)
 {
     if (trc->runtime()->spsProfiler.enabled() &&
@@ -1347,17 +1346,18 @@ IonScript::unlinkFromRuntime(FreeOp* fop
 
 void
 jit::ToggleBarriers(JS::Zone* zone, bool needs)
 {
     JSRuntime* rt = zone->runtimeFromMainThread();
     if (!rt->hasJitRuntime())
         return;
 
-    for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
+    for (gc::ZoneCellIterUnderGC i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+        JSScript* script = i.get<JSScript>();
         if (script->hasIonScript())
             script->ionScript()->toggleBarriers(needs);
         if (script->hasBaselineScript())
             script->baselineScript()->toggleBarriers(needs);
     }
 
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
         if (comp->jitCompartment())
--- a/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
+++ b/js/src/jsapi-tests/testGCStoreBufferRemoval.cpp
@@ -11,17 +11,17 @@
 using namespace JS;
 using namespace js;
 
 struct AutoIgnoreRootingHazards {
     // Force a nontrivial destructor so the compiler sees the whole RAII scope
     static volatile int depth;
     AutoIgnoreRootingHazards() { depth++; }
     ~AutoIgnoreRootingHazards() { depth--; }
-} JS_HAZ_GC_SUPPRESSED;
+};
 volatile int AutoIgnoreRootingHazards::depth = 0;
 
 BEGIN_TEST(testGCStoreBufferRemoval)
 {
     // Sanity check - objects start in the nursery and then become tenured.
     JS_GC(cx->runtime());
     JS::RootedObject obj(cx, NurseryObject());
     CHECK(js::gc::IsInsideNursery(obj.get()));
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -979,18 +979,18 @@ AddLazyFunctionsForCompartment(JSContext
     // an uncompiled enclosing script. The last condition is so that we don't
     // compile lazy scripts whose enclosing scripts failed to compile,
     // indicating that the lazy script did not escape the script.
     //
     // Some LazyScripts have a non-null |JSScript* script| pointer. We still
     // want to delazify in that case: this pointer is weak so the JSScript
     // could be destroyed at the next GC.
 
-    for (auto i = cx->zone()->cellIter<JSObject>(kind); !i.done(); i.next()) {
-        JSFunction* fun = &i->as<JSFunction>();
+    for (gc::ZoneCellIter i(cx->zone(), kind); !i.done(); i.next()) {
+        JSFunction* fun = &i.get<JSObject>()->as<JSFunction>();
 
         // Sweeping is incremental; take care to not delazify functions that
         // are about to be finalized. GC things referenced by objects that are
         // about to be finalized (e.g., in slots) may already be freed.
         if (gc::IsAboutToBeFinalizedUnbarriered(&fun) ||
             fun->compartment() != cx->compartment())
         {
             continue;
@@ -1152,17 +1152,18 @@ JSCompartment::clearScriptCounts()
 
     js_delete(scriptCountsMap);
     scriptCountsMap = nullptr;
 }
 
 void
 JSCompartment::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, HandleObject handler)
 {
-    for (auto script = zone()->cellIter<JSScript>(); !script.done(); script.next()) {
+    for (gc::ZoneCellIter i(zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+        JSScript* script = i.get<JSScript>();
         if (script->compartment() == this && script->hasAnyBreakpointsOrStepMode())
             script->clearBreakpointsIn(fop, dbg, handler);
     }
 }
 
 void
 JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                       size_t* tiAllocationSiteTables,
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2376,20 +2376,25 @@ Zone::prepareForCompacting()
 void
 GCRuntime::sweepTypesAfterCompacting(Zone* zone)
 {
     FreeOp* fop = rt->defaultFreeOp();
     zone->beginSweepTypes(fop, rt->gc.releaseObservedTypes && !zone->isPreservingCode());
 
     AutoClearTypeInferenceStateOnOOM oom(zone);
 
-    for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
+    for (ZoneCellIterUnderGC i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
+        JSScript* script = i.get<JSScript>();
         script->maybeSweepTypes(&oom);
-    for (auto group = zone->cellIter<ObjectGroup>(); !group.done(); group.next())
+    }
+
+    for (ZoneCellIterUnderGC i(zone, AllocKind::OBJECT_GROUP); !i.done(); i.next()) {
+        ObjectGroup* group = i.get<ObjectGroup>();
         group->maybeSweep(&oom);
+    }
 
     zone->types.endSweep(rt);
 }
 
 void
 GCRuntime::sweepZoneAfterCompacting(Zone* zone)
 {
     MOZ_ASSERT(zone->isCollecting());
@@ -3971,21 +3976,20 @@ CompartmentCheckTracer::onChild(const JS
 
 void
 GCRuntime::checkForCompartmentMismatches()
 {
     if (disableStrictProxyCheckingCount)
         return;
 
     CompartmentCheckTracer trc(rt);
-    AutoAssertEmptyNursery empty(rt);
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
         trc.zone = zone;
         for (auto thingKind : AllAllocKinds()) {
-            for (auto i = zone->cellIter<TenuredCell>(thingKind, empty); !i.done(); i.next()) {
+            for (ZoneCellIterUnderGC i(zone, thingKind); !i.done(); i.next()) {
                 trc.src = i.getCell();
                 trc.srcKind = MapAllocToTraceKind(thingKind);
                 trc.compartment = DispatchTraceKindTyped(MaybeCompartmentFunctor(),
                                                          trc.src, trc.srcKind);
                 js::TraceChildren(&trc, trc.src, trc.srcKind);
             }
         }
     }
@@ -3994,20 +3998,19 @@ GCRuntime::checkForCompartmentMismatches
 
 static void
 RelazifyFunctions(Zone* zone, AllocKind kind)
 {
     MOZ_ASSERT(kind == AllocKind::FUNCTION ||
                kind == AllocKind::FUNCTION_EXTENDED);
 
     JSRuntime* rt = zone->runtimeFromMainThread();
-    AutoAssertEmptyNursery empty(rt);
-
-    for (auto i = zone->cellIter<JSObject>(kind, empty); !i.done(); i.next()) {
-        JSFunction* fun = &i->as<JSFunction>();
+
+    for (ZoneCellIterUnderGC i(zone, kind); !i.done(); i.next()) {
+        JSFunction* fun = &i.get<JSObject>()->as<JSFunction>();
         if (fun->hasScript())
             fun->maybeRelazify(rt);
     }
 }
 
 bool
 GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock)
 {
@@ -6911,17 +6914,18 @@ gc::MergeCompartments(JSCompartment* sou
     // Fixup compartment pointers in source to refer to target, and make sure
     // type information generations are in sync.
 
     // Get the static global lexical scope of the target compartment. Static
     // scopes need to be fixed up below.
     RootedObject targetStaticGlobalLexicalScope(rt);
     targetStaticGlobalLexicalScope = &target->maybeGlobal()->lexicalScope().staticBlock();
 
-    for (auto script = source->zone()->cellIter<JSScript>(); !script.done(); script.next()) {
+    for (ZoneCellIter iter(source->zone(), AllocKind::SCRIPT); !iter.done(); iter.next()) {
+        JSScript* script = iter.get<JSScript>();
         MOZ_ASSERT(script->compartment() == source);
         script->compartment_ = target;
         script->setTypesGeneration(target->zone()->types.generation);
 
         // If the script failed to compile, no need to fix up.
         if (!script->code())
             continue;
 
@@ -6943,22 +6947,24 @@ gc::MergeCompartments(JSCompartment* sou
                     JSObject* enclosing = scope->enclosingScope();
                     if (IsStaticGlobalLexicalScope(enclosing))
                         scope->setEnclosingScope(targetStaticGlobalLexicalScope);
                 }
             }
         }
     }
 
-    for (auto base = source->zone()->cellIter<BaseShape>(); !base.done(); base.next()) {
+    for (ZoneCellIter iter(source->zone(), AllocKind::BASE_SHAPE); !iter.done(); iter.next()) {
+        BaseShape* base = iter.get<BaseShape>();
         MOZ_ASSERT(base->compartment() == source);
         base->compartment_ = target;
     }
 
-    for (auto group = source->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
+    for (ZoneCellIter iter(source->zone(), AllocKind::OBJECT_GROUP); !iter.done(); iter.next()) {
+        ObjectGroup* group = iter.get<ObjectGroup>();
         group->setGeneration(target->zone()->types.generation);
         group->compartment_ = target;
 
         // Remove any unboxed layouts from the list in the off thread
         // compartment. These do not need to be reinserted in the target
         // compartment's list, as the list is not required to be complete.
         if (UnboxedLayout* layout = group->maybeUnboxedLayoutDontCheckGeneration())
             layout->detachFromCompartment();
@@ -6970,17 +6976,18 @@ gc::MergeCompartments(JSCompartment* sou
         for (ArenaIter aiter(source->zone(), thingKind); !aiter.done(); aiter.next()) {
             Arena* arena = aiter.get();
             arena->zone = target->zone();
         }
     }
 
     // After fixing JSFunctions' compartments, we can fix LazyScripts'
     // enclosing scopes.
-    for (auto lazy = source->zone()->cellIter<LazyScript>(); !lazy.done(); lazy.next()) {
+    for (ZoneCellIter iter(source->zone(), AllocKind::LAZY_SCRIPT); !iter.done(); iter.next()) {
+        LazyScript* lazy = iter.get<LazyScript>();
         MOZ_ASSERT(lazy->functionNonDelazifying()->compartment() == target);
 
         // See warning in handleParseWorkload. If we start optimizing global
         // lexicals, we would need to merge the contents of the static global
         // lexical scope.
         if (JSObject* enclosing = lazy->enclosingScope()) {
             if (IsStaticGlobalLexicalScope(enclosing))
                 lazy->fixEnclosingStaticGlobalLexicalScope();
@@ -7104,19 +7111,22 @@ js::ReleaseAllJITCode(FreeOp* fop)
         zone->setPreservingCode(false);
         zone->discardJitCode(fop);
     }
 }
 
 void
 js::PurgeJITCaches(Zone* zone)
 {
-    /* Discard Ion caches. */
-    for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
+    for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
+        JSScript* script = i.get<JSScript>();
+
+        /* Discard Ion caches. */
         jit::PurgeCaches(script);
+    }
 }
 
 void
 ArenaLists::normalizeBackgroundFinalizeState(AllocKind thingKind)
 {
     ArenaLists::BackgroundFinalizeState* bfs = &backgroundFinalizeState[thingKind];
     switch (*bfs) {
       case BFS_DONE:
@@ -7376,17 +7386,18 @@ js::gc::CheckHashTablesAfterMovingGC(JSR
     /*
      * Check that internal hash tables no longer have any pointers to things
      * that have been moved.
      */
     rt->spsProfiler.checkStringsMapAfterMovingGC();
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
         zone->checkUniqueIdTableAfterMovingGC();
 
-        for (auto baseShape = zone->cellIter<BaseShape>(); !baseShape.done(); baseShape.next()) {
+        for (ZoneCellIterUnderGC i(zone, AllocKind::BASE_SHAPE); !i.done(); i.next()) {
+            BaseShape* baseShape = i.get<BaseShape>();
             if (baseShape->hasTable())
                 baseShape->table().checkAfterMovingGC();
         }
     }
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         c->objectGroups.checkTablesAfterMovingGC();
         c->dtoaCache.checkCacheAfterMovingGC();
         c->checkInitialShapesTableAfterMovingGC();
@@ -7854,33 +7865,10 @@ StateName(State state)
         "Finalize",
         "Compact"
     };
     MOZ_ASSERT(ArrayLength(names) == NUM_STATES);
     MOZ_ASSERT(state < NUM_STATES);
     return names[state];
 }
 
-void
-AutoAssertHeapBusy::checkCondition(JSRuntime *rt)
-{
-    this->rt = rt;
-    MOZ_ASSERT(rt->isHeapBusy());
-}
-
-void
-AutoAssertEmptyNursery::checkCondition(JSRuntime *rt) {
-    this->rt = rt;
-    MOZ_ASSERT(rt->gc.nursery.isEmpty());
-}
-
-AutoEmptyNursery::AutoEmptyNursery(JSRuntime *rt)
-  : AutoAssertEmptyNursery()
-{
-    MOZ_ASSERT(!rt->mainThread.suppressGC);
-    rt->gc.stats.suspendPhases();
-    rt->gc.evictNursery();
-    rt->gc.stats.resumePhases();
-    checkCondition(rt);
-}
-
 } /* namespace gc */
 } /* namespace js */
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1264,17 +1264,17 @@ MaybeVerifyBarriers(JSContext* cx, bool 
 #endif
 
 /*
  * Instances of this class set the |JSRuntime::suppressGC| flag for the duration
  * that they are live. Use of this class is highly discouraged. Please carefully
  * read the comment in vm/Runtime.h above |suppressGC| and take all appropriate
  * precautions before instantiating this class.
  */
-class MOZ_RAII JS_HAZ_GC_SUPPRESSED AutoSuppressGC
+class MOZ_RAII AutoSuppressGC
 {
     int32_t& suppressGC_;
 
   public:
     explicit AutoSuppressGC(ExclusiveContext* cx);
     explicit AutoSuppressGC(JSCompartment* comp);
     explicit AutoSuppressGC(JSRuntime* rt);
 
@@ -1323,105 +1323,16 @@ struct MOZ_RAII AutoAssertNoNurseryAlloc
 
   private:
     gc::GCRuntime& gc;
 #else
     explicit AutoAssertNoNurseryAlloc(JSRuntime* rt) {}
 #endif
 };
 
-/*
- * There are a couple of classes here that serve mostly as "tokens" indicating
- * that a condition holds. Some functions force the caller to possess such a
- * token because they would misbehave if the condition were false, and it is
- * far more clear to make the condition visible at the point where it can be
- * affected rather than just crashing in an assertion down in the place where
- * it is relied upon.
- */
-
-/*
- * Token meaning that the heap is busy and no allocations will be made.
- *
- * This class may be instantiated directly if it is known that the condition is
- * already true, or it can be used as a base class for another RAII class that
- * causes the condition to become true. Such base classes will use the no-arg
- * constructor, establish the condition, then call checkCondition() to assert
- * it and possibly record data needed to re-check the condition during
- * destruction.
- *
- * Ordinarily, you would do something like this with a Maybe<> member that is
- * emplaced during the constructor, but token-requiring functions want to
- * require a reference to a base class instance. That said, you can always pass
- * in the Maybe<> field as the token.
- */
-class MOZ_RAII AutoAssertHeapBusy {
-  protected:
-    JSRuntime* rt;
-
-    // Check that the heap really is busy, and record the rt for the check in
-    // the destructor.
-    void checkCondition(JSRuntime *rt);
-
-    AutoAssertHeapBusy() : rt(nullptr) {
-    }
-
-  public:
-    explicit AutoAssertHeapBusy(JSRuntime* rt) {
-        checkCondition(rt);
-    }
-
-    ~AutoAssertHeapBusy() {
-        MOZ_ASSERT(rt); // checkCondition must always be called.
-        checkCondition(rt);
-    }
-};
-
-/*
- * A class that serves as a token that the nursery is empty. It descends from
- * AutoAssertHeapBusy, which means that it additionally requires the heap to be
- * busy (which is not necessarily linked, but turns out to be true in practice
- * for all users and simplifies the usage of these classes.)
- */
-class MOZ_RAII AutoAssertEmptyNursery
-{
-  protected:
-    JSRuntime* rt;
-
-    // Check that the nursery is empty.
-    void checkCondition(JSRuntime *rt);
-
-    // For subclasses that need to empty the nursery in their constructors.
-    AutoAssertEmptyNursery() : rt(nullptr) {
-    }
-
-  public:
-    explicit AutoAssertEmptyNursery(JSRuntime* rt) {
-        checkCondition(rt);
-    }
-
-    ~AutoAssertEmptyNursery() {
-        checkCondition(rt);
-    }
-};
-
-/*
- * Evict the nursery upon construction. Serves as a token indicating that the
- * nursery is empty. (See AutoAssertEmptyNursery, above.)
- *
- * Note that this is very improper subclass of AutoAssertHeapBusy, in that the
- * heap is *not* busy within the scope of an AutoEmptyNursery. I will most
- * likely fix this by removing AutoAssertHeapBusy, but that is currently
- * waiting on jonco's review.
- */
-class MOZ_RAII AutoEmptyNursery : public AutoAssertEmptyNursery
-{
-  public:
-    explicit AutoEmptyNursery(JSRuntime *rt);
-};
-
 const char*
 StateName(State state);
 
 } /* namespace gc */
 
 #ifdef DEBUG
 /* Use this to avoid assertions when manipulating the wrapper map. */
 class MOZ_RAII AutoDisableProxyCheck
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -190,165 +190,103 @@ class ArenaCellIterUnderGC : public Aren
 };
 
 class ArenaCellIterUnderFinalize : public ArenaCellIterImpl
 {
   public:
     explicit ArenaCellIterUnderFinalize(Arena* arena) : ArenaCellIterImpl(arena) {}
 };
 
-template <typename T>
-class ZoneCellIter;
-
-template <>
-class ZoneCellIter<TenuredCell> {
+class ZoneCellIterImpl
+{
     ArenaIter arenaIter;
     ArenaCellIterImpl cellIter;
-    JS::AutoAssertNoAlloc noAlloc;
 
-  protected:
-    // For use when a subclass wants to insert some setup before init().
-    ZoneCellIter() {}
-
-    void init(JS::Zone* zone, AllocKind kind) {
+  public:
+    ZoneCellIterImpl(JS::Zone* zone, AllocKind kind) {
         JSRuntime* rt = zone->runtimeFromAnyThread();
         MOZ_ASSERT(zone);
         MOZ_ASSERT_IF(IsNurseryAllocable(kind), rt->gc.nursery.isEmpty());
 
-        // If called from outside a GC, ensure that the heap is in a state
-        // that allows us to iterate.
-        if (!rt->isHeapBusy()) {
-            // Assert that no GCs can occur while a ZoneCellIter is live.
-            noAlloc.disallowAlloc(rt);
-        }
-
         // We have a single-threaded runtime, so there's no need to protect
         // against other threads iterating or allocating. However, we do have
         // background finalization; we may have to wait for this to finish if
         // it's currently active.
         if (IsBackgroundFinalized(kind) && zone->arenas.needBackgroundFinalizeWait(kind))
             rt->gc.waitBackgroundSweepEnd();
+
         arenaIter.init(zone, kind);
         if (!arenaIter.done())
             cellIter.init(arenaIter.get());
     }
 
-  public:
-    ZoneCellIter(JS::Zone* zone, AllocKind kind) {
-        // If we are iterating a nursery-allocated kind then we need to
-        // evict first so that we can see all things.
-        if (IsNurseryAllocable(kind)) {
-            JSRuntime* rt = zone->runtimeFromMainThread();
-            rt->gc.evictNursery();
-        }
-
-        init(zone, kind);
-    }
-
-    ZoneCellIter(JS::Zone* zone, AllocKind kind, const js::gc::AutoAssertEmptyNursery&) {
-        // No need to evict the nursery. (This constructor is known statically
-        // to not GC.)
-        init(zone, kind);
-    }
-
     bool done() const {
         return arenaIter.done();
     }
 
-    template<typename T>
-    T* get() const {
+    template<typename T> T* get() const {
         MOZ_ASSERT(!done());
         return cellIter.get<T>();
     }
 
-    TenuredCell* getCell() const {
+    Cell* getCell() const {
         MOZ_ASSERT(!done());
         return cellIter.getCell();
     }
 
     void next() {
         MOZ_ASSERT(!done());
         cellIter.next();
         if (cellIter.done()) {
             MOZ_ASSERT(!arenaIter.done());
             arenaIter.next();
             if (!arenaIter.done())
                 cellIter.reset(arenaIter.get());
         }
     }
 };
 
-// Iterator over the cells in a Zone, where the GC type (JSString, JSObject) is
-// known, for a single AllocKind. Example usages:
-//
-//   for (auto obj = zone->cellIter<JSObject>(AllocKind::OBJECT0); !obj.done(); obj.next())
-//       ...
-//
-//   for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
-//       f(script->code());
-//
-// As this code demonstrates, you can use 'script' as if it were a JSScript*.
-// Its actual type is ZoneCellIter<JSScript>, but for most purposes it will
-// autoconvert to JSScript*.
-//
-// Note that in the JSScript case, ZoneCellIter is able to infer the AllocKind
-// from the type 'JSScript', whereas in the JSObject case, the kind must be
-// given (because there are multiple AllocKinds for objects).
-//
-// Also, the static rooting hazard analysis knows that the JSScript case will
-// not GC during construction. The JSObject case needs to GC, or more precisely
-// to empty the nursery and clear out the store buffer, so that it can see all
-// objects to iterate over (the nursery is not iterable) and remove the
-// possibility of having pointers from the store buffer to data hanging off
-// stuff we're iterating over that we are going to delete. (The latter should
-// not be a problem, since such instances should be using RelocatablePtr do
-// remove themselves from the store buffer on deletion, but currently for
-// subtle reasons that isn't good enough.)
-//
-// If the iterator is used within a GC, then there is no need to evict the
-// nursery (again). You may select a variant that will skip the eviction either
-// by specializing on a GCType that is never allocated in the nursery, or
-// explicitly by passing in a trailing AutoAssertEmptyNursery argument.
-//
-template <typename GCType>
-class ZoneCellIter : public ZoneCellIter<TenuredCell> {
+class ZoneCellIterUnderGC : public ZoneCellIterImpl
+{
+  public:
+    ZoneCellIterUnderGC(JS::Zone* zone, AllocKind kind)
+      : ZoneCellIterImpl(zone, kind)
+    {
+        MOZ_ASSERT(zone->runtimeFromAnyThread()->isHeapBusy());
+    }
+};
+
+class ZoneCellIter
+{
+    mozilla::Maybe<ZoneCellIterImpl> impl;
+    JS::AutoAssertNoAlloc noAlloc;
+
   public:
-    // Non-nursery allocated (equivalent to having an entry in
-    // MapTypeToFinalizeKind). The template declaration here is to discard this
-    // constructor overload if MapTypeToFinalizeKind<GCType>::kind does not
-    // exist. Note that there will be no remaining overloads that will work,
-    // which makes sense given that you haven't specified which of the
-    // AllocKinds to use for GCType.
-    //
-    // If we later add a nursery allocable GCType with a single AllocKind, we
-    // will want to add an overload of this constructor that does the right
-    // thing (ie, it empties the nursery before iterating.)
-    explicit ZoneCellIter(JS::Zone* zone) : ZoneCellIter<TenuredCell>() {
-        init(zone, MapTypeToFinalizeKind<GCType>::kind);
+    ZoneCellIter(JS::Zone* zone, AllocKind kind) {
+        // If called from outside a GC, ensure that the heap is in a state
+        // that allows us to iterate.
+        JSRuntime* rt = zone->runtimeFromMainThread();
+        if (!rt->isHeapBusy()) {
+            // If we are iterating a nursery-allocated kind then we need to
+            // evict first so that we can see all things.
+            if (IsNurseryAllocable(kind))
+                rt->gc.evictNursery();
+
+            // Assert that no GCs can occur while a ZoneCellIter is live.
+            noAlloc.disallowAlloc(rt);
+        }
+
+        impl.emplace(zone, kind);
     }
 
-    // Non-nursery allocated, nursery is known to be empty: same behavior as above.
-    ZoneCellIter(JS::Zone* zone, const js::gc::AutoAssertEmptyNursery&) : ZoneCellIter(zone) {
-    }
-
-    // Arbitrary kind, which will be assumed to be nursery allocable (and
-    // therefore the nursery will be emptied before iterating.)
-    ZoneCellIter(JS::Zone* zone, AllocKind kind) : ZoneCellIter<TenuredCell>(zone, kind) {
-    }
-
-    // Arbitrary kind, which will be assumed to be nursery allocable, but the
-    // nursery is known to be empty already: same behavior as non-nursery types.
-    ZoneCellIter(JS::Zone* zone, AllocKind kind, const js::gc::AutoAssertEmptyNursery& empty)
-      : ZoneCellIter<TenuredCell>(zone, kind, empty)
-    {
-    }
-
-    GCType* get() const { return ZoneCellIter<TenuredCell>::get<GCType>(); }
-    operator GCType*() const { return get(); }
-    GCType* operator ->() const { return get(); }
+    bool done() const { return impl->done(); }
+    template<typename T>
+    T* get() const { return impl->get<T>(); }
+    Cell* getCell() const { return impl->getCell(); }
+    void next() { impl->next(); }
 };
 
 class GCZonesIter
 {
   private:
     ZonesIter zone;
 
   public:
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -196,19 +196,18 @@ js::DumpPCCounts(JSContext* cx, HandleSc
         DumpIonScriptCounts(sp, script, ionCounts);
         ionCounts = ionCounts->previous();
     }
 }
 
 void
 js::DumpCompartmentPCCounts(JSContext* cx)
 {
-    RootedScript script(cx);
-    for (auto iter = cx->zone()->cellIter<JSScript>(); !iter.done(); iter.next()) {
-        script = iter;
+    for (ZoneCellIter i(cx->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+        RootedScript script(cx, i.get<JSScript>());
         if (script->compartment() != cx->compartment())
             continue;
 
         if (script->hasScriptCounts()) {
             Sprinter sprinter(cx);
             if (!sprinter.init())
                 return;
 
@@ -1653,17 +1652,18 @@ js::StopPCCountProfiling(JSContext* cx)
     ReleaseAllJITCode(rt->defaultFreeOp());
 
     auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>(cx,
         ScriptAndCountsVector(SystemAllocPolicy()));
     if (!vec)
         return;
 
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
+        for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             if (script->hasScriptCounts() && script->types()) {
                 if (!vec->append(script))
                     return;
             }
         }
     }
 
     rt->profilingScripts = false;
@@ -2020,17 +2020,18 @@ GenerateLcovInfo(JSContext* cx, JSCompar
     JSRuntime* rt = cx->runtime();
 
     // Collect the list of scripts which are part of the current compartment.
     {
         js::gc::AutoPrepareForTracing apft(rt, SkipAtoms);
     }
     Rooted<ScriptVector> topScripts(cx, ScriptVector(cx));
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
+        for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
+            JSScript* script = i.get<JSScript>();
             if (script->compartment() != comp ||
                 !script->isTopLevel() ||
                 !script->filename())
             {
                 continue;
             }
 
             if (!topScripts.append(script))
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2347,18 +2347,18 @@ UpdateExecutionObservabilityOfScriptsInZ
     {
         AutoEnterAnalysis enter(fop, zone);
         if (JSScript* script = obs.singleScriptForZoneInvalidation()) {
             if (obs.shouldRecompileOrInvalidate(script)) {
                 if (!AppendAndInvalidateScript(cx, zone, script, scripts))
                     return false;
             }
         } else {
-            for (auto iter = zone->cellIter<JSScript>(); !iter.done(); iter.next()) {
-                JSScript* script = iter;
+            for (gc::ZoneCellIter iter(zone, gc::AllocKind::SCRIPT); !iter.done(); iter.next()) {
+                JSScript* script = iter.get<JSScript>();
                 if (obs.shouldRecompileOrInvalidate(script) &&
                     !gc::IsAboutToBeFinalizedUnbarriered(&script))
                 {
                     if (!AppendAndInvalidateScript(cx, zone, script, scripts))
                         return false;
                 }
             }
         }
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1266,32 +1266,35 @@ GlobalHelperThreadState::mergeParseTaskC
     // destination compartment.  Finish any ongoing incremental GC first and
     // assert that no allocation can occur.
     gc::FinishGC(rt);
     JS::AutoAssertNoAlloc noAlloc(rt);
 
     LeaveParseTaskZone(rt, parseTask);
 
     {
+        gc::ZoneCellIter iter(parseTask->cx->zone(), gc::AllocKind::OBJECT_GROUP);
+
         // Generator functions don't have Function.prototype as prototype but a
         // different function object, so the IdentifyStandardPrototype trick
         // below won't work.  Just special-case it.
         GlobalObject* parseGlobal = &parseTask->exclusiveContextGlobal->as<GlobalObject>();
         JSObject* parseTaskStarGenFunctionProto = parseGlobal->getStarGeneratorFunctionPrototype();
 
         // Module objects don't have standard prototypes either.
         JSObject* moduleProto = parseGlobal->maybeGetModulePrototype();
         JSObject* importEntryProto = parseGlobal->maybeGetImportEntryPrototype();
         JSObject* exportEntryProto = parseGlobal->maybeGetExportEntryPrototype();
 
         // Point the prototypes of any objects in the script's compartment to refer
         // to the corresponding prototype in the new compartment. This will briefly
         // create cross compartment pointers, which will be fixed by the
         // MergeCompartments call below.
-        for (auto group = parseTask->cx->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
+        for (; !iter.done(); iter.next()) {
+            ObjectGroup* group = iter.get<ObjectGroup>();
             TaggedProto proto(group->proto());
             if (!proto.isObject())
                 continue;
 
             JSObject* protoObj = proto.toObject();
 
             JSObject* newProto;
             JSProtoKey key = JS::IdentifyStandardPrototype(protoObj);
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1044,17 +1044,17 @@ CallAddPropertyHookDense(ExclusiveContex
             obj->setDenseElementHole(cx, index);
             return false;
         }
     }
     return true;
 }
 
 static bool
-UpdateShapeTypeAndValue(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape, const Value& value)
+UpdateShapeTypeAndValue(ExclusiveContext* cx, NativeObject* obj, Shape* shape, const Value& value)
 {
     jsid id = shape->propid();
     if (shape->hasSlot()) {
         obj->setSlotWithType(cx, shape, value, /* overwriting = */ false);
 
         // Per the acquired properties analysis, when the shape of a partially
         // initialized object is changed to its fully initialized shape, its
         // group can be updated as well.
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2540,25 +2540,26 @@ js::PrintTypes(JSContext* cx, JSCompartm
     JSAutoRequest request(cx);
 
     Zone* zone = comp->zone();
     AutoEnterAnalysis enter(nullptr, zone);
 
     if (!force && !InferSpewActive(ISpewResult))
         return;
 
-    RootedScript script(cx);
-    for (auto iter = zone->cellIter<JSScript>(); !iter.done(); iter.next()) {
-        script = iter;
+    for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+        RootedScript script(cx, i.get<JSScript>());
         if (script->types())
             script->types()->printTypes(cx, script);
     }
 
-    for (auto group = zone->cellIter<ObjectGroup>(); !group.done(); group.next())
+    for (gc::ZoneCellIter i(zone, gc::AllocKind::OBJECT_GROUP); !i.done(); i.next()) {
+        ObjectGroup* group = i.get<ObjectGroup>();
         group->print();
+    }
 #endif
 }
 
 /////////////////////////////////////////////////////////////////////
 // ObjectGroup
 /////////////////////////////////////////////////////////////////////
 
 static inline void
@@ -4419,29 +4420,30 @@ TypeZone::endSweep(JSRuntime* rt)
     sweepReleaseTypes = false;
 
     rt->gc.freeAllLifoBlocksAfterSweeping(&sweepTypeLifoAlloc);
 }
 
 void
 TypeZone::clearAllNewScriptsOnOOM()
 {
-    for (auto iter = zone()->cellIter<ObjectGroup>(); !iter.done(); iter.next()) {
-        ObjectGroup* group = iter;
+    for (gc::ZoneCellIter iter(zone(), gc::AllocKind::OBJECT_GROUP);
+         !iter.done(); iter.next())
+    {
+        ObjectGroup* group = iter.get<ObjectGroup>();
         if (!IsAboutToBeFinalizedUnbarriered(&group))
             group->maybeClearNewScriptOnOOM();
     }
 }
 
 AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM()
 {
     if (oom) {
-        JSRuntime* rt = zone->runtimeFromMainThread();
         zone->setPreservingCode(false);
-        zone->discardJitCode(rt->defaultFreeOp());
+        zone->discardJitCode(zone->runtimeFromMainThread()->defaultFreeOp());
         zone->types.clearAllNewScriptsOnOOM();
     }
 }
 
 #ifdef DEBUG
 void
 TypeScript::printTypes(JSContext* cx, HandleScript script) const
 {