diff --git a/emcc b/emcc
index a78fa890131c2..fd4e4c75ff6cd 100755
--- a/emcc
+++ b/emcc
@@ -860,6 +860,11 @@ try:
   # Apply optimization level settings
   shared.Settings.apply_opt_level(opt_level, noisy=True)
 
+  fastcomp = os.environ.get('EMCC_FAST_COMPILER') != '0'
+  if fastcomp:
+    # Set ASM_JS default here so that we can override it from the command line.
+    shared.Settings.ASM_JS = 1 if opt_level > 0 else 2
+
   # Apply -s settings in newargs here (after optimization levels, so they can override them)
   for change in settings_changes:
     key, value = change.split('=')
@@ -871,11 +876,9 @@ try:
     if key == 'EXPORTED_FUNCTIONS':
       shared.Settings.ORIGINAL_EXPORTED_FUNCTIONS = shared.Settings.EXPORTED_FUNCTIONS[:] # used for warnings in emscripten.py
 
-  fastcomp = os.environ.get('EMCC_FAST_COMPILER') != '0'
-
   if fastcomp:
-    shared.Settings.ASM_JS = 1 if opt_level > 0 else 2
     try:
+      assert shared.Settings.ASM_JS > 0, 'ASM_JS must be enabled in fastcomp'
       assert shared.Settings.UNALIGNED_MEMORY == 0, 'forced unaligned memory not supported in fastcomp'
       assert shared.Settings.CHECK_HEAP_ALIGN == 0, 'check heap align not supported in fastcomp - use SAFE_HEAP instead'
       assert shared.Settings.SAFE_DYNCALLS == 0, 'safe dyncalls not supported in fastcomp'
@@ -932,7 +935,7 @@ try:
       js_opts = True
       logging.warning('enabling js opts to allow SAFE_HEAP to work properly')
 
-    if shared.Settings.ALLOW_MEMORY_GROWTH:
+    if shared.Settings.ALLOW_MEMORY_GROWTH and shared.Settings.ASM_JS != 2:
       logging.warning('Disabling asm.js validation for memory growth (memory can grow, but you lose some amount of speed)');
       shared.Settings.ASM_JS = 2
 
diff --git a/emscripten-version.txt b/emscripten-version.txt
index 362c239d66da5..b1a41036cde93 100644
--- a/emscripten-version.txt
+++ b/emscripten-version.txt
@@ -1,2 +1,2 @@
-1.27.2
+1.28.0
 
diff --git a/src/library.js b/src/library.js
index 432b4880f002b..711cc7296a4a6 100644
--- a/src/library.js
+++ b/src/library.js
@@ -1878,7 +1878,7 @@ LibraryManager.library = {
           }
           unget();
         }
-        if (buffer.length === 0) return 0;  // Failure.
+        if (buffer.length === 0) return fields; // Stop here.
         if (suppressAssignment) continue;
 
         var text = buffer.join('');
diff --git a/src/library_glfw.js b/src/library_glfw.js
index 7998022485c9b..25da42bab5bff 100644
--- a/src/library_glfw.js
+++ b/src/library_glfw.js
@@ -527,6 +527,11 @@ var LibraryGLFW = {
     },
 
     getCursorPos: function(winid, x, y) {
+      setValue(x, Browser.mouseX, 'double');
+      setValue(y, Browser.mouseY, 'double');
+    },
+
+    getMousePos: function(winid, x, y) {
       setValue(x, Browser.mouseX, 'i32');
       setValue(y, Browser.mouseY, 'i32');
     },
@@ -1145,7 +1150,7 @@ var LibraryGLFW = {
   },
 
   glfwGetMousePos: function(x, y) {
-    GLFW.getCursorPos(GLFW.active.id, x, y);
+    GLFW.getMousePos(GLFW.active.id, x, y);
   },
 
   glfwSetMousePos: function(x, y) {
@@ -1188,12 +1193,12 @@ var LibraryGLFW = {
     _sleep(time);
   },
 
-  glfwEnable: function(token) {
+  glfwEnable: function(target) {
     target = GLFW.GLFW2ParamToGLFW3Param(target);
     GLFW.hints[target] = false;
   },
 
-  glfwDisable: function(token) {
+  glfwDisable: function(target) {
     target = GLFW.GLFW2ParamToGLFW3Param(target);
     GLFW.hints[target] = true;
   },
diff --git a/src/library_html5.js b/src/library_html5.js
index 57f37ae5aca60..b2409328f8ee9 100644
--- a/src/library_html5.js
+++ b/src/library_html5.js
@@ -207,6 +207,10 @@ var LibraryJSEvents = {
       JSEvents.registerOrRemoveHandler(eventHandler);
     },
 
+    getBoundingClientRectOrZeros: function(target) {
+      return target.getBoundingClientRect ? target.getBoundingClientRect() : { left: 0, top: 0 };
+    },
+
     // Copies mouse event data from the given JS mouse event 'e' to the specified Emscripten mouse event structure in the HEAP.
     // eventStruct: the structure to populate.
     // e: The JS mouse event to read data from.
@@ -235,7 +239,7 @@ var LibraryJSEvents = {
         {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.canvasY, '0', 'i32') }}};
       }
       if (target) {
-        var rect = (target === window) ? { left: 0, top: 0 } : target.getBoundingClientRect();
+        var rect = JSEvents.getBoundingClientRectOrZeros(target);
         {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.targetX, 'e.clientX - rect.left', 'i32') }}};
         {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.targetY, 'e.clientY - rect.top', 'i32') }}};        
       } else { // No specific target passed, return 0.
@@ -810,7 +814,7 @@ var LibraryJSEvents = {
         {{{ makeSetValue('ptr', C_STRUCTS.EmscriptenTouchEvent.metaKey, 'e.metaKey', 'i32') }}};
         ptr += {{{ C_STRUCTS.EmscriptenTouchEvent.touches }}}; // Advance to the start of the touch array.
         var canvasRect = Module['canvas'] ? Module['canvas'].getBoundingClientRect() : undefined;
-        var targetRect = (target === window) ? { left: 0, top: 0 } : target.getBoundingClientRect();
+        var targetRect = JSEvents.getBoundingClientRectOrZeros(target);
         var numTouches = 0;
         for(var i in touches) {
           var t = touches[i];
diff --git a/src/library_sdl.js b/src/library_sdl.js
index befa4e4b32c9d..839f7f1b2f6a4 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -491,7 +491,7 @@ var LibrarySDL = {
           SDL.updateRect(dstrect, dr);
         }
       }
-      var blitw, blitr;
+      var blitw, blith;
       if (scale) {
         blitw = dr.w; blith = dr.h;
       } else {
diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h
index 1ec344927b106..7f9de9ab8404c 100644
--- a/system/include/emscripten/bind.h
+++ b/system/include/emscripten/bind.h
@@ -1379,7 +1379,7 @@ namespace emscripten {
         typedef std::vector<T> VecType;
 
         void (VecType::*push_back)(const T&) = &VecType::push_back;
-        void (VecType::*resize)(const size_t) = &VecType::resize;
+        void (VecType::*resize)(const size_t, const T&) = &VecType::resize;
         return class_<std::vector<T>>(name)
             .template constructor<>()
             .function("push_back", push_back)
diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js
index 1b29c5ad79755..141f5bc747fd0 100644
--- a/tests/embind/embind.test.js
+++ b/tests/embind/embind.test.js
@@ -918,23 +918,23 @@ module({
             vec.delete();
         });
 
-        test("resize appends the default value", function() {
+        test("resize appends the given value", function() {
             var vec = cm.emval_test_return_vector();
 
-            vec.resize(5);
+            vec.resize(5, 42);
             assert.equal(5, vec.size());
             assert.equal(10, vec.get(0));
             assert.equal(20, vec.get(1));
             assert.equal(30, vec.get(2));
-            assert.equal(0, vec.get(3));
-            assert.equal(0, vec.get(4));
+            assert.equal(42, vec.get(3));
+            assert.equal(42, vec.get(4));
             vec.delete();
         });
 
         test("resize preserves content when shrinking", function() {
             var vec = cm.emval_test_return_vector();
 
-            vec.resize(2);
+            vec.resize(2, 42);
             assert.equal(2, vec.size());
             assert.equal(10, vec.get(0));
             assert.equal(20, vec.get(1));
diff --git a/tests/float_tex.cpp b/tests/float_tex.cpp
index c40ff78668dbb..698b058e88671 100644
--- a/tests/float_tex.cpp
+++ b/tests/float_tex.cpp
@@ -61,7 +61,13 @@ static void updateFloatTexture() {
         ++count;
     }
     glBindTexture(GL_TEXTURE_2D, nodeTexture);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data);
+#ifdef __EMSCRIPTEN__ // In GLES2 and WebGL1, we must use unsized texture internal formats.
+    const GLenum internalFormat = GL_RGBA;
+#else
+    // In desktop GL, we can also use sized internal formats.
+    const GLenum internalFormat = GL_RGBA32F;
+#endif
+    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glBindTexture(GL_TEXTURE_2D, NULL);
@@ -77,7 +83,7 @@ static void glut_draw_callback(void) {
     glActiveTexture(GL_TEXTURE0);
     updateFloatTexture(); //we change the texture each time to create the effect (it is just for the test)
     glBindTexture(GL_TEXTURE_2D, nodeTexture);
-    glUniform1i(nodeSamplerLocation, GL_TEXTURE0);
+    glUniform1i(nodeSamplerLocation, 0);
     glEnableVertexAttribArray(0);
     glBindBuffer(GL_ARRAY_BUFFER, indicesVBO);
     glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, NULL);
@@ -117,9 +123,11 @@ static void gl_init(void) {
     /* Get the locations of the uniforms so we can access them */
     nodeSamplerLocation      = glGetUniformLocation(program, "nodeInfo");
     glBindAttribLocation(program, 0, "indices");
+#ifndef __EMSCRIPTEN__ // GLES2 & WebGL do not have these, only pre 3.0 desktop GL and compatibility mode GL3.0+ GL do.
     //Enable glPoint size in shader, always enable in Open Gl ES 2.
     glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
     glEnable(GL_POINT_SPRITE);
+#endif
 }
 int main(int argc, char *argv[]) {
     glutInit(&argc, argv);
diff --git a/tests/gl_subdata.cpp b/tests/gl_subdata.cpp
index 5c6a992686f96..52508a4a3ec35 100644
--- a/tests/gl_subdata.cpp
+++ b/tests/gl_subdata.cpp
@@ -62,6 +62,13 @@ static void updateFloatTexture() {
     }
     glBindTexture(GL_TEXTURE_2D, nodeTexture);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data);
+#ifdef __EMSCRIPTEN__ // In GLES2 and WebGL1, we must use unsized texture internal formats.
+    const GLenum internalFormat = GL_RGBA;
+#else
+    // In desktop GL, we can also use sized internal formats.
+    const GLenum internalFormat = GL_RGBA32F;
+#endif
+    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glBindTexture(GL_TEXTURE_2D, NULL);
@@ -77,7 +84,7 @@ static void glut_draw_callback(void) {
     glActiveTexture(GL_TEXTURE0);
     updateFloatTexture(); //we change the texture each time to create the effect (it is just for the test)
     glBindTexture(GL_TEXTURE_2D, nodeTexture);
-    glUniform1i(nodeSamplerLocation, GL_TEXTURE0);
+    glUniform1i(nodeSamplerLocation, 0);
     glEnableVertexAttribArray(0);
     glBindBuffer(GL_ARRAY_BUFFER, indicesVBO);
     glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, NULL);
@@ -122,9 +129,11 @@ static void gl_init(void) {
     /* Get the locations of the uniforms so we can access them */
     nodeSamplerLocation      = glGetUniformLocation(program, "nodeInfo");
     glBindAttribLocation(program, 0, "indices");
+#ifndef __EMSCRIPTEN__ // GLES2 & WebGL do not have these, only pre 3.0 desktop GL and compatibility mode GL3.0+ GL do.
     //Enable glPoint size in shader, always enable in Open Gl ES 2.
     glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
     glEnable(GL_POINT_SPRITE);
+#endif
 }
 int main(int argc, char *argv[]) {
     glutInit(&argc, argv);
diff --git a/tests/glfw.c b/tests/glfw.c
index 0a7a358b28fe8..2a247603ae573 100644
--- a/tests/glfw.c
+++ b/tests/glfw.c
@@ -49,6 +49,8 @@ void Init()
   if (glfwInit() != GL_TRUE)
     Shut_Down(1);
 
+  glfwEnable(GLFW_KEY_REPEAT); // test for issue #3059
+
   int red_bits = glfwGetWindowParam(GLFW_RED_BITS);
   glfwOpenWindowHint(GLFW_RED_BITS, 8);
   assert(glfwGetWindowParam(GLFW_RED_BITS) == 8);
diff --git a/tests/hello_world_gles_proxy.c b/tests/hello_world_gles_proxy.c
index 1a8e5f61da278..7e977fed64d0b 100644
--- a/tests/hello_world_gles_proxy.c
+++ b/tests/hello_world_gles_proxy.c
@@ -40,6 +40,7 @@
 
 #define _GNU_SOURCE
 
+#include <assert.h>
 #include <math.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -613,11 +614,19 @@ gears_idle(void)
    static double tRot0 = -1.0, tRate0 = -1.0;
    double dt, t = glutGet(GLUT_ELAPSED_TIME) / 1000.0;
 
+#ifdef STATIC_GEARS
+   assert(t - tRot0 > 0); // Test that time does advance even though we are rendering a static image.
+#endif
+
    if (tRot0 < 0.0)
       tRot0 = t;
    dt = t - tRot0;
    tRot0 = t;
 
+#ifdef STATIC_GEARS
+   dt = t = 0.0; // Render the gears in a static position.
+#endif
+
    /* advance rotation for next frame */
    angle += 70.0 * dt;  /* 70 degrees per second */
    if (angle > 3600.0)
diff --git a/tests/test_browser.py b/tests/test_browser.py
index 51f257412a6a4..ddd4849ac6dbb 100644
--- a/tests/test_browser.py
+++ b/tests/test_browser.py
@@ -758,7 +758,7 @@ def test_sdl_canvas_proxy(self):
     self.btest('sdl_canvas_proxy.c', reference='sdl_canvas_proxy.png', args=['--proxy-to-worker', '--preload-file', 'data.txt'], manual_reference=True, post_build=self.post_manual_reftest)
 
   def test_glgears_proxy(self):
-    self.btest('hello_world_gles_proxy.c', reference='gears.png', args=['--proxy-to-worker', '-s', 'GL_TESTING=1'], manual_reference=True, post_build=self.post_manual_reftest)
+    self.btest('hello_world_gles_proxy.c', reference='gears.png', args=['--proxy-to-worker', '-s', 'GL_TESTING=1', '-DSTATIC_GEARS=1'], manual_reference=True, post_build=self.post_manual_reftest)
 
     # test noProxy option applied at runtime
 
diff --git a/tests/test_core.py b/tests/test_core.py
index 7e297b434094f..e49c6a737b10c 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -4347,6 +4347,35 @@ def test_fscanf(self):
     self.emcc_args += ['--embed-file', 'three_numbers.txt']
     self.do_run(src, 'match = 3\nx = -1.0, y = 0.1, z = -0.1\n')
 
+  def test_fscanf_2(self):
+    if self.emcc_args is None: return self.skip('requires emcc')
+
+    open('a.txt', 'w').write('''1/2/3 4/5/6 7/8/9
+''')
+    self.emcc_args += ['--embed-file', 'a.txt']
+    self.do_run(r'''#include <cstdio>
+#include <iostream>
+
+using namespace std;
+
+int
+main( int argv, char ** argc ) {
+    cout << "fscanf test" << endl;
+
+    FILE * file;
+    file = fopen("a.txt", "rb");
+    int vertexIndex[4];
+    int normalIndex[4];
+    int uvIndex[4];
+
+    int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex    [1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2], &vertexIndex[3], &uvIndex[3], &normalIndex[3]); 
+
+    cout << matches << endl;
+
+    return 0;
+}
+''', 'fscanf test\n9\n')
+
   def test_fileno(self):
     if self.emcc_args is None: return self.skip('requires emcc')
     open(os.path.join(self.get_dir(), 'empty.txt'), 'w').write('')
@@ -7232,7 +7261,7 @@ def setUp(self):
   return TT
 
 # Main test modes
-default = make_run("default", compiler=CLANG, emcc_args=[])
+default = make_run("default", compiler=CLANG, emcc_args=["-s", "ASM_JS=2"])
 asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1"])
 asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"])
 asm3 = make_run("asm3", compiler=CLANG, emcc_args=["-O3"])
diff --git a/tests/test_other.py b/tests/test_other.py
index 6b5807f05ee9e..e88ff62376f10 100644
--- a/tests/test_other.py
+++ b/tests/test_other.py
@@ -118,13 +118,13 @@ def test_emcc(self):
         (['-o', 'something.js', '-O2'],                   2, None, 0, 1),
         (['-o', 'something.js', '-O2', '-g'],             2, None, 0, 0),
         (['-o', 'something.js', '-Os'],                   2, None, 0, 1),
-        (['-o', 'something.js', '-O3', '-s', 'ASM_JS=0'], 3, None, 0, 1),
+        (['-o', 'something.js', '-O3'],                   3, None, 0, 1),
         # and, test compiling to bitcode first
         (['-o', 'something.bc'], 0, [],      0, 0),
         (['-o', 'something.bc', '-O0'], 0, [], 0, 0),
         (['-o', 'something.bc', '-O1'], 1, ['-O1'], 0, 0),
         (['-o', 'something.bc', '-O2'], 2, ['-O2'], 0, 0),
-        (['-o', 'something.bc', '-O3'], 3, ['-O3', '-s', 'ASM_JS=0'], 0, 0),
+        (['-o', 'something.bc', '-O3'], 3, ['-O3'], 0, 0),
         (['-O1', '-o', 'something.bc'], 1, [], 0, 0),
       ]:
         print params, opt_level, bc_params, closure, has_malloc
diff --git a/tests/test_sanity.py b/tests/test_sanity.py
index 24b352be3778f..02c0954bed90d 100644
--- a/tests/test_sanity.py
+++ b/tests/test_sanity.py
@@ -149,12 +149,12 @@ def test_closure_compiler(self):
     f = open(CONFIG_FILE, 'a')
     f.write('CLOSURE_COMPILER = "/tmp/nowhere/nothingtoseehere/kjadsfkjwelkjsdfkqgas/nonexistent.txt"\n')
     f.close()
-    output = self.check_working([EMCC, '-O2', '-s', 'ASM_JS=0', '--closure', '1', 'tests/hello_world.cpp'], CLOSURE_FATAL)
+    output = self.check_working([EMCC, '-O2', '-s', '--closure', '1', 'tests/hello_world.cpp'], CLOSURE_FATAL)
 
     # With a working path, all is well
     restore()
     try_delete('a.out.js')
-    output = self.check_working([EMCC, '-O2', '-s', 'ASM_JS=0', '--closure', '1', 'tests/hello_world.cpp'], '')
+    output = self.check_working([EMCC, '-O2', '-s', '--closure', '1', 'tests/hello_world.cpp'], '')
     assert os.path.exists('a.out.js'), output
 
   def test_llvm(self):
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index a7ba7e9f39fe7..b94c2beaa0340 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -2872,7 +2872,7 @@ function registerizeHarder(ast) {
     function createReg(forName) {
       // Create a new register of type suitable for the given variable name.
       var allRegs = allRegsByType[localVars[forName]];
-      reg = nextReg++;
+      var reg = nextReg++;
       allRegs[reg] = regPrefixByType[localVars[forName]] + reg;
       return reg;
     }
@@ -3027,7 +3027,7 @@ function registerizeHarder(ast) {
       // Look through value-preserving casts, like "x | 0" => "x"
       if (node[0] === 'binary' && node[1] === '|') {
         if (node[3][0] === 'num' && node[3][1] === 0) {
-            return lookThroughCasts(node[2]);
+          return lookThroughCasts(node[2]);
         }
       }
       return node;
@@ -3202,7 +3202,7 @@ function registerizeHarder(ast) {
             joinJunction(jCondExit);
             joinJunction(jLoop);
             setJunction(jCondExit);
-            joinJunction(jExit)
+            joinJunction(jExit);
           }
           break;
         case 'for':
@@ -3273,7 +3273,7 @@ function registerizeHarder(ast) {
             setJunction(jCheckExit);
           }
           joinJunction(jExit);
-          popActiveLabels()
+          popActiveLabels();
           break;
         case 'return':
           if (node[1]) {
@@ -3654,9 +3654,9 @@ function registerizeHarder(ast) {
           junctionVariables[name].conf[otherName] = 1;
         }
         for (var b in junc.outblocks) {
-          // It conflits with any output vars of successor blocks,
+          // It conflicts with any output vars of successor blocks,
           // if they're assigned before it goes dead in that block.
-          block = blocks[b];
+          var block = blocks[b];
           var jSucc = junctions[block.exit];
           for (var otherName in jSucc.live) {
             if (junc.live[otherName]) continue;
@@ -3755,7 +3755,7 @@ function registerizeHarder(ast) {
       // Mark the point at which each input reg becomes dead.
       // Variables alive before this point must not be assigned
       // to that register.
-      var inputVars = {}
+      var inputVars = {};
       var inputDeadLoc = {};
       var inputVarsByReg = {};
       for (var name in jExit.live) {
@@ -3865,7 +3865,7 @@ function registerizeHarder(ast) {
 
     // Assign registers to function params based on entry junction
 
-    var paramRegs = {}
+    var paramRegs = {};
     if (fun[2]) {
       for (var i = 0; i < fun[2].length; i++) {
         var allRegs = allRegsByType[localVars[fun[2][i]]];
diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp
index b5f7ddfff71e0..beca2b08c4475 100644
--- a/tools/optimizer/optimizer.cpp
+++ b/tools/optimizer/optimizer.cpp
@@ -2,6 +2,7 @@
 #include <cstdio>
 #include <cmath>
 #include <string>
+#include <algorithm>
 
 #include "simple_ast.h"
 
@@ -80,6 +81,18 @@ enum AsmType {
   ASM_NONE = 5 // number of types
 };
 
+AsmType intToAsmType(int type) {
+  switch (type) {
+    case 0: return ASM_INT;
+    case 1: return ASM_DOUBLE;
+    case 2: return ASM_FLOAT;
+    case 3: return ASM_FLOAT32X4;
+    case 4: return ASM_INT32X4;
+    case 5: return ASM_NONE;
+    default: assert(0); return ASM_NONE;
+  }
+}
+
 // forward decls
 struct AsmData;
 AsmType detectType(Ref node, AsmData *asmData=nullptr, bool inVarDef=false);
@@ -765,6 +778,8 @@ StringSet ASSOCIATIVE_BINARIES("+ * | & ^"),
           CONDITION_CHECKERS("if do while switch"),
           SAFE_TO_DROP_COERCION("unary-prefix name num");
 
+StringSet BREAK_CAPTURERS("do while for switch"),
+          FUNCTIONS_THAT_ALWAYS_THROW("abort ___resumeException ___cxa_throw ___cxa_rethrow");
 
 bool isFunctionTable(const char *name) {
   static const char *functionTable = "FUNCTION_TABLE";
@@ -1351,7 +1366,7 @@ void eliminate(Ref ast, bool memSafe=false) {
         node[1] = node[1].filter(function(pair) { return !varsToRemove[pair[0]] });
         if (node[1]->size() == 0) {
           // wipe out an empty |var;|
-          node[0] = 'toplevel';
+          node[0] = TOPLEVEL;
           node[1] = [];
         }
       } else */
@@ -2189,6 +2204,32 @@ void optimizeFrounds(Ref ast) {
 }
 
 // Very simple 'registerization', coalescing of variables into a smaller number.
+
+const char* getRegPrefix(AsmType type) {
+  switch (type) {
+    case ASM_INT:       return"i"; break;
+    case ASM_DOUBLE:    return "d"; break;
+    case ASM_FLOAT:     return "f"; break;
+    case ASM_FLOAT32X4: return "F4"; break;
+    case ASM_INT32X4:   return "I4"; break;
+    case ASM_NONE:      return "Z"; break;
+    default: assert(0); // type doesn't have a name yet
+  }
+  return nullptr;
+}
+
+IString getRegName(AsmType type, int num) {
+  const char* str = getRegPrefix(type);
+  int size = strlen(str) + int(ceil(log10(num))) + 3;
+  char temp[size];
+  int written = sprintf(temp, "%s%d", str, num);
+  assert(written < size);
+  temp[written] = 0;
+  IString ret;
+  ret.set(temp, false);
+  return ret;
+}
+
 void registerize(Ref ast) {
   traverseFunctions(ast, [](Ref fun) {
     AsmData asmData(fun);
@@ -2222,23 +2263,9 @@ void registerize(Ref ast) {
     auto getNewRegName = [&](int num, IString name) {
       const char *str;
       AsmType type = asmData.getType(name);
-      switch (type) {
-        case ASM_INT:       str = "i"; break;
-        case ASM_DOUBLE:    str = "d"; break;
-        case ASM_FLOAT:     str = "f"; break;
-        case ASM_FLOAT32X4: str = "F4"; break;
-        case ASM_INT32X4:   str = "I4"; break;
-        case ASM_NONE:      str = "Z"; break;
-        default: assert(0); // type doesn't have a name yet
-      }
-      int size = strlen(str) + int(ceil(log10(num))) + 3;
-      char *temp = (char*)malloc(size);
-      int written = sprintf(temp, "%s%d", str, num);
-      assert(written < size);
-      temp[written] = 0;
-      IString ret(temp); // likely interns a new string; leaks if not XXX FIXME
-      regTypes[ret] = type;
+      IString ret = getRegName(type, num);
       assert(!allVars.has(ret) || asmData.isLocal(ret)); // register must not shadow non-local name
+      regTypes[ret] = type;
       return ret;
     };
     // Find the # of uses of each variable.
@@ -2433,6 +2460,1086 @@ void registerize(Ref ast) {
   });
 }
 
+// Assign variables to 'registers', coalescing them onto a smaller number of shared
+// variables.
+//
+// This does the same job as 'registerize' above, but burns a lot more cycles trying
+// to reduce the total number of register variables.  Key points about the operation:
+//
+//   * we decompose the AST into a flow graph and perform a full liveness
+//     analysis, to determine which variables are live at each point.
+//
+//   * variables that are live concurrently are assigned to different registers.
+//
+//   * variables that are linked via 'x=y' style statements are assigned the same
+//     register if possible, so that the redundant assignment can be removed.
+//     (e.g. assignments used to pass state around through loops).
+//
+//   * any code that cannot be reached through the flow-graph is removed.
+//     (e.g. redundant break statements like 'break L123; break;').
+//
+//   * any assignments that we can prove are not subsequently used are removed.
+//     (e.g. unnecessary assignments to the 'label' variable).
+//
+void registerizeHarder(Ref ast) {
+  traverseFunctions(ast, [](Ref fun) {
+
+    // Do not try to process non-validating methods, like the heap replacer
+    bool abort = false;
+    traversePre(fun, [&abort](Ref node) {
+      if (node[0] == NEW) abort = true;
+    });
+    if (abort) return;
+
+    AsmData asmData(fun);
+
+    // Utilities for allocating register variables.
+    // We need distinct register pools for each type of variable.
+
+    typedef std::unordered_map<int, IString> IntStringMap;
+    std::vector<IntStringMap> allRegsByType;
+    allRegsByType.resize(ASM_NONE+1);
+    int nextReg = 1;
+
+    auto createReg = [&](IString forName) {
+      // Create a new register of type suitable for the given variable name.
+      AsmType type = asmData.getType(forName);
+      IntStringMap& allRegs = allRegsByType[type];
+      int reg = nextReg++;
+      allRegs[reg] = getRegName(type, reg);
+      return reg;
+    };
+
+    // Traverse the tree in execution order and synthesize a basic flow-graph.
+    // It's convenient to build a kind of "dual" graph where the nodes identify
+    // the junctions between blocks  at which control-flow may branch, and each
+    // basic block is an edge connecting two such junctions.
+    // For each junction we store:
+    //    * set of blocks that originate at the junction
+    //    * set of blocks that terminate at the junction
+    // For each block we store:
+    //    * a single entry junction
+    //    * a single exit junction
+    //    * a 'use' and 'kill' set of names for the block
+    //    * full sequence of NAME and ASSIGN nodes in the block
+    //    * whether each such node appears as part of a larger expression
+    //      (and therefore cannot be safely eliminated)
+    //    * set of labels that can be used to jump to this block
+
+    struct Junction {
+      int id;
+      std::unordered_set<int> inblocks, outblocks;
+      StringSet live;
+      Junction(int id_) : id(id_) {}
+    };
+    struct Node {
+    };
+    struct Block {
+      int id, entry, exit;
+      StringSet labels;
+      std::vector<Ref> nodes;
+      std::vector<bool> isexpr;
+      StringIntMap use;
+      StringSet kill;
+      StringStringMap link;
+      StringIntMap lastUseLoc;
+      StringIntMap firstDeadLoc;
+      StringIntMap firstKillLoc;
+      StringIntMap lastKillLoc;
+
+      Block() : id(-1), entry(-1), exit(-1) {}
+    };
+    struct ContinueBreak {
+      int co, br;
+      ContinueBreak() : co(-1), br(-1) {}
+      ContinueBreak(int co_, int br_) : co(co_), br(br_) {}
+    };
+    typedef std::unordered_map<IString, ContinueBreak> LabelState;
+
+    std::vector<Junction> junctions;
+    std::vector<Block*> blocks;
+    int currEntryJunction = -1;
+    Block* nextBasicBlock = nullptr;
+    int isInExpr = 0;
+    std::vector<LabelState> activeLabels;
+    IString nextLoopLabel;
+
+    const int ENTRY_JUNCTION = 0;
+    const int EXIT_JUNCTION = 1;
+    const int ENTRY_BLOCK = 0;
+
+    auto addJunction = [&]() {
+      // Create a new junction, without inserting it into the graph.
+      // This is useful for e.g. pre-allocating an exit node.
+      int id = junctions.size();
+      junctions.push_back(Junction(id));
+      return id;
+    };
+
+    std::function<int (int, bool)> joinJunction;
+
+    auto markJunction = [&](int id=-1) {
+      // Mark current traversal location as a junction.
+      // This makes a new basic block exiting at this position.
+      if (id < 0) {
+        id = addJunction();
+      }
+      joinJunction(id, true);
+      return id;
+    };
+
+    auto setJunction = [&](int id, bool force) {
+      // Set the next entry junction to the given id.
+      // This can be used to enter at a previously-declared point.
+      // You can't return to a junction with no incoming blocks
+      // unless the 'force' parameter is specified.
+      assert(nextBasicBlock->nodes.size() == 0); // refusing to abandon an in-progress basic block
+      if (force || junctions[id].inblocks.size() > 0) {
+        currEntryJunction = id;
+      } else {
+        currEntryJunction = -1;
+      }
+    };
+
+    joinJunction = [&](int id, bool force) {
+      // Complete the pending basic block by exiting at this position.
+      // This can be used to exit at a previously-declared point.
+      if (currEntryJunction >= 0) {
+        assert(nextBasicBlock);
+        nextBasicBlock->id = blocks.size();
+        nextBasicBlock->entry = currEntryJunction;
+        nextBasicBlock->exit = id;
+        junctions[currEntryJunction].outblocks.insert(nextBasicBlock->id);
+        junctions[id].inblocks.insert(nextBasicBlock->id);
+        blocks.push_back(nextBasicBlock);
+      } 
+      nextBasicBlock = new Block();
+      setJunction(id, force);
+      return id;
+    };
+
+    IString NULL_STR;
+
+    auto pushActiveLabels = [&](int onContinue, int onBreak) {
+      // Push the target junctions for continuing/breaking a loop.
+      // This should be called before traversing into a loop.
+      LabelState& prevLabels = activeLabels.back();
+      LabelState newLabels = prevLabels;
+      newLabels[NULL_STR] = ContinueBreak(onContinue, onBreak);
+      if (!!nextLoopLabel) {
+        newLabels[nextLoopLabel] = ContinueBreak(onContinue, onBreak);
+        nextLoopLabel = NULL_STR;
+      }
+      // An unlabelled CONTINUE should jump to innermost loop,
+      // ignoring any nested SWITCH statements.
+      if (onContinue < 0 && prevLabels.count(NULL_STR) > 0) {
+        newLabels[NULL_STR].co = prevLabels[NULL_STR].co;
+      }
+      activeLabels.push_back(newLabels);
+    };
+
+    auto popActiveLabels = [&]() {
+      // Pop the target junctions for continuing/breaking a loop.
+      // This should be called after traversing into a loop.
+      activeLabels.pop_back();
+    };
+
+    auto markNonLocalJump = [&](IString type, IString label) {
+      // Complete a block via  RETURN, BREAK or CONTINUE.
+      // This joins the targetted junction and then sets the current junction to null.
+      // Any code traversed before we get back an existing junction is dead code.
+      if (type == RETURN) {
+        joinJunction(EXIT_JUNCTION, false);
+      } else {
+        assert(activeLabels.back().count(label) > 0); // 'jump to unknown label');
+        auto targets = activeLabels.back()[label];
+        if (type == CONTINUE) {
+          joinJunction(targets.co, false);
+        } else if (type == BREAK) {
+          joinJunction(targets.br, false);
+        } else {
+          assert(0); // 'unknown jump node type');
+        }
+      }
+      currEntryJunction = -1;
+    };
+
+    auto addUseNode = [&](Ref node) {
+      // Mark a use of the given name node in the current basic block.
+      assert(node[0] == NAME); // 'not a use node');
+      IString name = node[1]->getIString();
+      if (asmData.isLocal(name)) {
+        nextBasicBlock->nodes.push_back(node);
+        nextBasicBlock->isexpr.push_back(isInExpr);
+        if (nextBasicBlock->kill.count(name) == 0) {
+          nextBasicBlock->use[name] = 1;
+        }
+      }
+    };
+
+    auto addKillNode = [&](Ref node) {
+      // Mark an assignment to the given name node in the current basic block.
+      assert(node[0] == ASSIGN); //, 'not a kill node');
+      assert(node[1]->isBool(true)); // 'not a kill node');
+      assert(node[2][0] == NAME); //, 'not a kill node');
+      IString name = node[2][1]->getIString();
+      if (asmData.isLocal(name)) {
+        nextBasicBlock->nodes.push_back(node);
+        nextBasicBlock->isexpr.push_back(isInExpr);
+        nextBasicBlock->kill.insert(name);
+      }
+    };
+
+    std::function<Ref (Ref)> lookThroughCasts = [&](Ref node) {
+      // Look through value-preserving casts, like "x | 0" => "x"
+      if (node[0] == BINARY && node[1] == OR) {
+        if (node[3][0] == NUM && node[3][1]->getNumber() == 0) {
+          return lookThroughCasts(node[2]);
+        }
+      }
+      return node;
+    };
+
+    auto addBlockLabel = [&](Ref node) {
+      assert(nextBasicBlock->nodes.size() == 0); // 'cant add label to an in-progress basic block')
+      if (node[0] == NUM) {
+        nextBasicBlock->labels.insert(node[1]->getIString()); // XXX?
+      }
+    };
+
+    auto isTrueNode = [&](Ref node) {
+      // Check if the given node is statically truthy.
+      return (node[0] == NUM && node[1]->getNumber() != 0);
+    };
+
+    auto isFalseNode = [&](Ref node) {
+      // Check if the given node is statically falsy.
+      return (node[0] == NUM && node[1]->getNumber() == 0);
+    };
+
+    std::function<void (Ref)> buildFlowGraph = [&](Ref node) {
+      // Recursive function to build up the flow-graph.
+      // It walks the tree in execution order, calling the above state-management
+      // functions at appropriate points in the traversal.
+      Ref type = node[0];
+  
+      // Any code traversed without an active entry junction must be dead,
+      // as the resulting block could never be entered. Let's remove it.
+      if (currEntryJunction < 0 && junctions.size() > 0) {
+        safeCopy(node, makeBlock());
+        return;
+      }
+ 
+      // Traverse each node type according to its particular control-flow semantics.
+      // TODO: switchify this
+      if (type == DEFUN) {
+        int jEntry = markJunction();
+        assert(jEntry == ENTRY_JUNCTION);
+        int jExit = addJunction();
+        assert(jExit == EXIT_JUNCTION);
+        for (int i = 0; i < node[3]->size(); i++) {
+          buildFlowGraph(node[3][i]);
+        }
+        joinJunction(jExit, false);
+      } else if (type == IF) {
+        isInExpr++;
+        buildFlowGraph(node[1]);
+        isInExpr--;
+        int jEnter = markJunction();
+        int jExit = addJunction();
+        if (!!node[2]) {
+          // Detect and mark "if (label == N) { <labelled block> }".
+          if (node[1][0] == BINARY && node[1][1] == EQ) {
+            Ref lhs = lookThroughCasts(node[1][2]);
+            if (lhs[0] == NAME && lhs[1] == LABEL) {
+              addBlockLabel(lookThroughCasts(node[1][3]));
+            }
+          }
+          buildFlowGraph(node[2]);
+        }
+        joinJunction(jExit, false);
+        setJunction(jEnter, false);
+        if (!!node[3]) {
+          buildFlowGraph(node[3]);
+        }
+        joinJunction(jExit, false);
+      } else if (type == CONDITIONAL) {
+        isInExpr++;
+        // If the conditional has no side-effects, we can treat it as a single
+        // block, which might open up opportunities to remove it entirely.
+        if (!hasSideEffects(node)) {
+          buildFlowGraph(node[1]);
+          if (!!node[2]) {
+            buildFlowGraph(node[2]);
+          }
+          if (!!node[3]) {
+            buildFlowGraph(node[3]);
+          }
+        } else {
+          buildFlowGraph(node[1]);
+          int jEnter = markJunction();
+          int jExit = addJunction();
+          if (!!node[2]) {
+            buildFlowGraph(node[2]);
+          }
+          joinJunction(jExit, false);
+          setJunction(jEnter, false);
+          if (!!node[3]) {
+            buildFlowGraph(node[3]);
+          }
+          joinJunction(jExit, false);
+        }
+        isInExpr--;
+      } else if (type == WHILE) {
+        // Special-case "while (1) {}" to use fewer junctions,
+        // since emscripten generates a lot of these.
+        if (isTrueNode(node[1])) {
+          int jLoop = markJunction();
+          int jExit = addJunction();
+          pushActiveLabels(jLoop, jExit);
+          buildFlowGraph(node[2]);
+          popActiveLabels();
+          joinJunction(jLoop, false);
+          setJunction(jExit, false);
+        } else {
+          int jCond = markJunction();
+          int jLoop = addJunction();
+          int jExit = addJunction();
+          isInExpr++;
+          buildFlowGraph(node[1]);
+          isInExpr--;
+          joinJunction(jLoop, false);
+          pushActiveLabels(jCond, jExit);
+          buildFlowGraph(node[2]);
+          popActiveLabels();
+          joinJunction(jCond, false);
+          // An empty basic-block linking condition exit to loop exit.
+          setJunction(jLoop, false);
+          joinJunction(jExit, false);
+        }
+      } else if (type == DO) {
+        // Special-case "do {} while (1)" and "do {} while (0)" to use
+        // fewer junctions, since emscripten generates a lot of these.
+        if (isFalseNode(node[1])) {
+          int jExit = addJunction();
+          pushActiveLabels(jExit, jExit);
+          buildFlowGraph(node[2]);
+          popActiveLabels();
+          joinJunction(jExit, false);
+        } else if (isTrueNode(node[1])) {
+          int jLoop = markJunction();
+          int jExit = addJunction();
+          pushActiveLabels(jLoop, jExit);
+          buildFlowGraph(node[2]);
+          popActiveLabels();
+          joinJunction(jLoop, false);
+          setJunction(jExit, false);
+        } else {
+          int jLoop = markJunction();
+          int jCond = addJunction();
+          int jCondExit = addJunction();
+          int jExit = addJunction();
+          pushActiveLabels(jCond, jExit);
+          buildFlowGraph(node[2]);
+          popActiveLabels();
+          joinJunction(jCond, false);
+          isInExpr++;
+          buildFlowGraph(node[1]);
+          isInExpr--;
+          joinJunction(jCondExit, false);
+          joinJunction(jLoop, false);
+          setJunction(jCondExit, false);
+          joinJunction(jExit, false);
+        }
+      } else if (type == FOR) {
+        int jTest = addJunction();
+        int jBody = addJunction();
+        int jStep = addJunction();
+        int jExit = addJunction();
+        buildFlowGraph(node[1]);
+        joinJunction(jTest, false);
+        isInExpr++;
+        buildFlowGraph(node[2]);
+        isInExpr--;
+        joinJunction(jBody, false);
+        pushActiveLabels(jStep, jExit);
+        buildFlowGraph(node[4]);
+        popActiveLabels();
+        joinJunction(jStep, false);
+        buildFlowGraph(node[3]);
+        joinJunction(jTest, false);
+        setJunction(jBody, false);
+        joinJunction(jExit, false);
+      } else if (type == LABEL) {
+        assert(BREAK_CAPTURERS.has(node[2][0])); // 'label on non-loop, non-switch statement')
+        nextLoopLabel = node[1]->getIString();
+        buildFlowGraph(node[2]);
+      } else if (type == SWITCH) {
+        // Emscripten generates switch statements of a very limited
+        // form: all case clauses are numeric literals, and all
+        // case bodies end with a (maybe implicit) break.  So it's
+        // basically equivalent to a multi-way IF statement.
+        isInExpr++;
+        buildFlowGraph(node[1]);
+        isInExpr--;
+        Ref condition = lookThroughCasts(node[1]);
+        int jCheckExit = markJunction();
+        int jExit = addJunction();
+        pushActiveLabels(-1, jExit);
+        bool hasDefault = false;
+        for (int i = 0; i < node[2]->size(); i++) {
+          setJunction(jCheckExit, false);
+          // All case clauses are either 'default' or a numeric literal.
+          if (!node[2][i][0]) {
+            hasDefault = true;
+          } else {
+            // Detect switches dispatching to labelled blocks.
+            if (condition[0] == NAME && condition[1] == LABEL) {
+              addBlockLabel(lookThroughCasts(node[2][i][0]));
+            }
+          }
+          for (int j = 0; j < node[2][i][1]->size(); j++) {
+            buildFlowGraph(node[2][i][1][j]);
+          }
+          // Control flow will never actually reach the end of the case body.
+          // If there's live code here, assume it jumps to case exit.
+          if (currEntryJunction >= 0 && nextBasicBlock->nodes.size() > 0) {
+            if (!!node[2][i][0]) {
+              markNonLocalJump(RETURN, IString());
+            } else {
+              joinJunction(jExit, false);
+            }
+          }
+        }
+        // If there was no default case, we also need an empty block
+        // linking straight from the test evaluation to the exit.
+        if (!hasDefault) {
+          setJunction(jCheckExit, false);
+        }
+        joinJunction(jExit, false);
+        popActiveLabels();
+      } else if (type == RETURN) {
+        if (!!node[1]) {
+          isInExpr++;
+          buildFlowGraph(node[1]);
+          isInExpr--;
+        }
+        markNonLocalJump(type->getIString(), IString());
+      } else if (type == BREAK || type == CONTINUE) {
+        markNonLocalJump(type->getIString(), node[1]->getIString());
+      } else if (type == ASSIGN) {
+        isInExpr++;
+        buildFlowGraph(node[3]);
+        isInExpr--;
+        if (node[1]->isBool(true) && node[2][0] == NAME) {
+          addKillNode(node);
+        } else {
+          buildFlowGraph(node[2]);
+        }
+      } else if (type == NAME) {
+        addUseNode(node);
+      } else if (type == BLOCK || type == TOPLEVEL) {
+        if (!!node[1]) {
+          for (int i = 0; i < node[1]->size(); i++) {
+            buildFlowGraph(node[1][i]);
+          }
+        }
+      } else if (type == STAT) {
+        buildFlowGraph(node[1]);
+      } else if (type == UNARY_PREFIX || type == UNARY_POSTFIX) {
+        isInExpr++;
+        buildFlowGraph(node[2]);
+        isInExpr--;
+      } else if (type == BINARY) {
+        isInExpr++;
+        buildFlowGraph(node[2]);
+        buildFlowGraph(node[3]);
+        isInExpr--;
+      } else if (type == CALL) {
+        isInExpr++;
+        buildFlowGraph(node[1]);
+        if (!!node[2]) {
+          for (int i = 0; i < node[2]->size(); i++) {
+            buildFlowGraph(node[2][i]);
+          }
+        }
+        isInExpr--;
+        // If the call is statically known to throw,
+        // treat it as a jump to function exit.
+        if (!isInExpr && node[1][0] == NAME) {
+          if (FUNCTIONS_THAT_ALWAYS_THROW.has(node[1][1])) {
+            markNonLocalJump(RETURN, IString());
+          }
+        }
+      } else if (type == SEQ || type == SUB) {
+        isInExpr++;
+        buildFlowGraph(node[1]);
+        buildFlowGraph(node[2]);
+        isInExpr--;
+      } else if (type == DOT || type == THROW) {
+        isInExpr++;
+        buildFlowGraph(node[1]);
+        isInExpr--;
+      } else if (type == NUM || type == STRING || type == VAR) {
+        // nada
+      } else {
+        assert(0); // 'unsupported node type: ' + type);
+      }
+    };
+
+    buildFlowGraph(fun);
+
+    assert(junctions[ENTRY_JUNCTION].inblocks.size() == 0); // 'function entry must have no incoming blocks');
+    assert(junctions[EXIT_JUNCTION].outblocks.size() == 0); // 'function exit must have no outgoing blocks');
+    assert(blocks[ENTRY_BLOCK]->entry == ENTRY_JUNCTION); //, 'block zero must be the initial block');
+
+    // Fix up implicit jumps done by assigning to the LABEL variable.
+    // If a block ends with an assignment to LABEL and there's another block
+    // with that value of LABEL as precondition, we tweak the flow graph so
+    // that the former jumps straight to the later.
+
+    std::unordered_map<IString, Block*> labelledBlocks;
+    typedef std::pair<Ref, Block*> Jump;
+    std::vector<Jump> labelledJumps;
+
+    for (int i = 0; i < blocks.size(); i++) {
+      Block* block = blocks[i];
+      // Does it have any labels as preconditions to its entry?
+      for (auto labelVal : block->labels) {
+        // If there are multiple blocks with the same label, all bets are off.
+        // This seems to happen sometimes for short blocks that end with a return.
+        // TODO: it should be safe to merge the duplicates if they're identical.
+        if (labelledBlocks.count(labelVal) > 0) {
+          labelledBlocks.clear();
+          labelledJumps.clear();
+          goto AFTER_FINDLABELLEDBLOCKS;
+        }
+        labelledBlocks[labelVal] = block;
+      }
+      // Does it assign a specific label value at exit?
+      if (block->kill.has(LABEL)) {
+        Ref finalNode = block->nodes.back();
+        if (finalNode[0] == ASSIGN && finalNode[2][1] == LABEL) {
+          // If labels are computed dynamically then all bets are off.
+          // This can happen due to indirect branching in llvm output.
+          if (finalNode[3][0] != NUM) {
+            labelledBlocks.clear();
+            labelledJumps.clear();
+            goto AFTER_FINDLABELLEDBLOCKS;
+          }
+          labelledJumps.push_back(Jump(finalNode[3][1], block));
+        } else { 
+          // If label is assigned a non-zero value elsewhere in the block
+          // then all bets are off.  This can happen e.g. due to outlining
+          // saving/restoring label to the stack.
+          for (int j = 0; j < block->nodes.size() - 1; j++) {
+            if (block->nodes[j][0] == ASSIGN && block->nodes[j][2][1] == LABEL) {
+              if (block->nodes[j][3][0] != NUM && block->nodes[j][3][1]->getNumber() != 0) {
+                labelledBlocks.clear();
+                labelledJumps.clear();
+                goto AFTER_FINDLABELLEDBLOCKS;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    AFTER_FINDLABELLEDBLOCKS:
+
+    for (auto labelVal : labelledBlocks) {
+      Block* block = labelVal.second;
+      // Disconnect it from the graph, and create a
+      // new junction for jumps targetting this label.
+      junctions[block->entry].outblocks.erase(block->id);
+      block->entry = addJunction();
+      junctions[block->entry].outblocks.insert(block->id);
+      // Add a fake use of LABEL to keep it alive in predecessor.
+      block->use[LABEL] = 1;
+      block->nodes.insert(block->nodes.begin(), makeName(LABEL));
+      block->isexpr.insert(block->isexpr.begin(), 1);
+    }
+    for (int i = 0; i < labelledJumps.size(); i++) {
+      auto labelVal = labelledJumps[i].first;
+      auto block = labelledJumps[i].second;
+      Block* targetBlock = labelledBlocks[labelVal->getIString()];
+      if (targetBlock) {
+        // Redirect its exit to entry of the target block.
+        junctions[block->exit].inblocks.erase(block->id);
+        block->exit = targetBlock->entry;
+        junctions[block->exit].inblocks.insert(block->id);
+      }
+    }
+
+    // Do a backwards data-flow analysis to determine the set of live
+    // variables at each junction, and to use this information to eliminate
+    // any unused assignments.
+    // We run two nested phases.  The inner phase builds the live set for each
+    // junction.  The outer phase uses this to try to eliminate redundant
+    // stores in each basic block, which might in turn affect liveness info.
+
+    auto analyzeJunction = [&](Junction& junc) {
+      // Update the live set for this junction.
+      StringSet live;
+      for (auto b : junc.outblocks) {
+        Block* block = blocks[b];
+        StringSet& liveSucc = junctions[block->exit].live;
+        for (auto name : liveSucc) {
+          if (!block->kill.has(name)) {
+            live.insert(name);
+          }
+        }
+        for (auto name : block->use) {
+          live.insert(name.first);
+        }
+      }
+      junc.live = live;
+    };
+
+    auto analyzeBlock = [&](Block* block) {
+      // Update information about the behaviour of the block.
+      // This includes the standard 'use' and 'kill' information,
+      // plus a 'link' set naming values that flow through from entry
+      // to exit, possibly changing names via simple 'x=y' assignments.
+      // As we go, we eliminate assignments if the variable is not
+      // subsequently used.
+      auto live = junctions[block->exit].live;
+      StringIntMap use;
+      StringSet kill;
+      StringStringMap link;
+      StringIntMap lastUseLoc;
+      StringIntMap firstDeadLoc;
+      StringIntMap firstKillLoc;
+      StringIntMap lastKillLoc;
+      for (auto name : live) {
+        link[name] = name;
+        lastUseLoc[name] = block->nodes.size();
+        firstDeadLoc[name] = block->nodes.size();
+      }
+      for (int j = block->nodes.size() - 1; j >= 0 ; j--) {
+        Ref node = block->nodes[j];
+        if (node[0] == NAME) {
+          IString name = node[1]->getIString();
+          live.insert(name);
+          use[name] = j;
+          if (lastUseLoc.count(name) == 0) {
+            lastUseLoc[name] = j;
+            firstDeadLoc[name] = j;
+          }
+        } else {
+          IString name = node[2][1]->getIString();
+          // We only keep assignments if they will be subsequently used.
+          if (live.has(name)) {
+            kill.insert(name);
+            use.erase(name);
+            live.erase(name);
+            firstDeadLoc[name] = j;
+            firstKillLoc[name] = j;
+            if (lastUseLoc.count(name) == 0) {
+              lastUseLoc[name] = j;
+            }
+            if (lastKillLoc.count(name) == 0) {
+              lastKillLoc[name] = j;
+            }
+            // If it's an "x=y" and "y" is not live, then we can create a
+            // flow-through link from "y" to "x".  If not then there's no
+            // flow-through link for "x".
+            if (link.has(name)) {
+              IString oldLink = link[name];
+              link.erase(name);
+              if (node[3][0] == NAME) {
+                if (asmData.isLocal(node[3][1]->getIString())) {
+                  link[node[3][1]->getIString()] = oldLink;
+                }
+              }
+            }
+          } else {
+            // The result of this assignment is never used, so delete it.
+            // We may need to keep the RHS for its value or its side-effects.
+            auto removeUnusedNodes = [&](int j, int n) {
+              for (auto pair : lastUseLoc) {
+                pair.second -= n;
+              }
+              for (auto pair : firstKillLoc) {
+                pair.second -= n;
+              }
+              for (auto pair : lastKillLoc) {
+                pair.second -= n;
+              }
+              for (auto pair : firstDeadLoc) {
+                pair.second -= n;
+              }
+              block->nodes.erase(block->nodes.begin() + j, block->nodes.begin() + j + n);
+              block->isexpr.erase(block->isexpr.begin() + j, block->isexpr.begin() + j + n);
+            };
+            if (block->isexpr[j] || hasSideEffects(node[3])) {
+              safeCopy(node, node[3]);
+              removeUnusedNodes(j, 1);
+            } else {
+              int numUsesInExpr = 0;
+              traversePre(node[3], [&](Ref node) {
+                if (node[0] == NAME && asmData.isLocal(node[1]->getIString())) {
+                  numUsesInExpr++;
+                }
+              });
+              safeCopy(node, makeBlock());
+              j = j - numUsesInExpr;
+              removeUnusedNodes(j, 1 + numUsesInExpr);
+            }
+          }
+        }
+      }
+      // XXX efficiency
+      block->use = use;
+      block->kill = kill;
+      block->link = link;
+      block->lastUseLoc = lastUseLoc;
+      block->firstDeadLoc = firstDeadLoc;
+      block->firstKillLoc = firstKillLoc;
+      block->lastKillLoc = lastKillLoc;
+    };
+
+    std::unordered_set<int> jWorklistMap;
+    jWorklistMap.insert(EXIT_JUNCTION);
+    std::vector<int> jWorklist;
+    jWorklist.push_back(EXIT_JUNCTION);
+    std::unordered_set<int> bWorklistMap;
+    std::vector<int> bWorklist;
+
+    // Be sure to visit every junction at least once.
+    // This avoids missing some vars because we disconnected them
+    // when processing the labelled jumps.
+    for (int i = junctions.size() - 1; i >= EXIT_JUNCTION; i--) {
+      jWorklistMap.insert(i);
+      jWorklist.push_back(i);
+    }
+
+    while (jWorklist.size() > 0) {
+      // Iterate on just the junctions until we get stable live sets.
+      // The first run of this loop will grow the live sets to their maximal size.
+      // Subsequent runs will shrink them based on eliminated in-block uses.
+      while (jWorklist.size() > 0) {
+        Junction& junc = junctions[jWorklist.back()];
+        jWorklist.pop_back();
+        jWorklistMap.erase(junc.id);
+        StringSet oldLive = junc.live; // copy it here, to check for changes later
+        analyzeJunction(junc);
+        if (oldLive != junc.live) {
+          // Live set changed, updated predecessor blocks and junctions.
+          for (auto b : junc.inblocks) {
+            if (bWorklistMap.count(b) == 0) {
+              bWorklistMap.insert(b);
+              bWorklist.push_back(b);
+            }
+            int jPred = blocks[b]->entry;
+            if (jWorklistMap.count(jPred) == 0) {
+              jWorklistMap.insert(jPred);
+              jWorklist.push_back(jPred);
+            }
+          }
+        }
+      }
+      // Now update the blocks based on the calculated live sets.
+      while (bWorklist.size() > 0) {
+        Block* block = blocks[bWorklist.back()];
+        bWorklist.pop_back();
+        bWorklistMap.erase(block->id);
+        auto oldUse = block->use;
+        analyzeBlock(block);
+        if (oldUse != block->use) {
+          // The use set changed, re-process the entry junction.
+          if (jWorklistMap.count(block->entry) == 0) {
+            jWorklistMap.insert(block->entry);
+            jWorklist.push_back(block->entry);
+          }
+        }
+      }
+    }
+
+    // Insist that all function parameters are alive at function entry.
+    // This ensures they will be assigned independent registers, even
+    // if they happen to be unused.
+
+    for (auto name : asmData.params) {
+      junctions[ENTRY_JUNCTION].live.insert(name);
+    }
+
+    // For variables that are live at one or more junctions, we assign them
+    // a consistent register for the entire scope of the function.  Find pairs
+    // of variable that cannot use the same register (the "conflicts") as well
+    // as pairs of variables that we'd like to have share the same register
+    // (the "links").
+
+    struct JuncVar {
+      StringSet conf, link;
+      std::unordered_set<int> excl;
+      int reg;
+      JuncVar() : reg(-1) {}
+    };
+    std::unordered_map<IString, JuncVar> junctionVariables;
+
+    auto initializeJunctionVariable = [&](IString name) {
+      junctionVariables[name] = JuncVar(); // XXX
+    };
+
+    for (int i = 0; i < junctions.size(); i++) {
+      Junction& junc = junctions[i];
+      for (auto name : junc.live) {
+        if (junctionVariables.count(name) == 0) initializeJunctionVariable(name);
+        // It conflicts with all other names live at this junction.
+        for (auto otherName : junc.live) {
+          if (otherName == name) continue;
+          junctionVariables[name].conf.insert(otherName);
+        }
+        for (auto b : junc.outblocks) {
+          // It conflicts with any output vars of successor blocks,
+          // if they're assigned before it goes dead in that block.
+          Block* block = blocks[b];
+          Junction& jSucc = junctions[block->exit];
+          for (auto otherName : jSucc.live) {
+            if (junc.live.has(otherName)) continue;
+            if (block->lastKillLoc[otherName] < block->firstDeadLoc[name]) {
+              if (junctionVariables.count(otherName) == 0) initializeJunctionVariable(otherName);
+              junctionVariables[name].conf.insert(otherName);
+              junctionVariables[otherName].conf.insert(name);
+            }
+          }
+          // It links with any linkages in the outgoing blocks.
+          IString linkName = block->link[name];
+          if (!!linkName && linkName != name) {
+            if (junctionVariables.count(linkName) == 0) initializeJunctionVariable(linkName);
+            junctionVariables[name].link.insert(linkName);
+            junctionVariables[linkName].link.insert(name);
+          }
+        }
+      }
+    }
+
+    // Attempt to sort the junction variables to heuristically reduce conflicts.
+    // Simple starting point: handle the most-conflicted variables first.
+    // This seems to work pretty well.
+
+    StringVec sortedJunctionVariables;
+    for (auto pair : junctionVariables) {
+      sortedJunctionVariables.push_back(pair.first);
+    }
+    std::sort(sortedJunctionVariables.begin(), sortedJunctionVariables.end(), [&](const IString name1, const IString name2) {
+      return junctionVariables[name2].conf.size() < junctionVariables[name1].conf.size(); //XXX
+    });
+
+    // We can now assign a register to each junction variable.
+    // Process them in order, trying available registers until we find
+    // one that works, and propagating the choice to linked/conflicted
+    // variables as we go.
+
+    std::function<bool (IString, int)> tryAssignRegister = [&](IString name, int reg) {
+      // Try to assign the given register to the given variable,
+      // and propagate that choice throughout the graph.
+      // Returns true if successful, false if there was a conflict.
+      JuncVar& jv = junctionVariables[name];
+      if (jv.reg > 0) {
+        return jv.reg == reg;
+      }
+      if (jv.excl.count(reg) > 0) {
+        return false;
+      }
+      jv.reg = reg;
+      // Exclude use of this register at all conflicting variables.
+      for (auto confName : jv.conf) {
+        junctionVariables[confName].excl.insert(reg);
+      }
+      // Try to propagate it into linked variables.
+      // It's not an error if we can't.
+      for (auto linkName : jv.link) {
+        tryAssignRegister(linkName, reg);
+      }
+      return true;
+    };
+
+    for (int i = 0; i < sortedJunctionVariables.size(); i++) {
+      IString name = sortedJunctionVariables[i];
+      // It may already be assigned due to linked-variable propagation.
+      if (!!junctionVariables[name].reg) {
+        continue;
+      }
+      // Try to use existing registers first.
+      auto& allRegs = allRegsByType[asmData.getType(name)];
+      bool moar = false;
+      for (auto reg : allRegs) {
+        if (tryAssignRegister(name, reg.first)) {
+          moar = true;
+          break;
+        }
+      }
+      if (moar) continue;
+      // They're all taken, create a new one.
+      tryAssignRegister(name, createReg(name));
+    }
+
+    // Each basic block can now be processed in turn.
+    // There may be internal-use-only variables that still need a register
+    // assigned, but they can be treated just for this block.  We know
+    // that all inter-block variables are in a good state thanks to
+    // junction variable consistency.
+
+    for (int i = 0; i < blocks.size(); i++) {
+      Block* block = blocks[i];
+      if (block->nodes.size() == 0) continue;
+      Junction& jEnter = junctions[block->entry];
+      Junction& jExit = junctions[block->exit];
+      // Mark the point at which each input reg becomes dead.
+      // Variables alive before this point must not be assigned
+      // to that register.
+      StringSet inputVars;
+      std::unordered_map<int, int> inputDeadLoc;
+      std::unordered_map<int, IString> inputVarsByReg;
+      for (auto name : jExit.live) {
+        if (!block->kill.has(name)) {
+          inputVars.insert(name);
+          int reg = junctionVariables[name].reg;
+          assert(reg > 0); // 'input variable doesnt have a register');
+          inputDeadLoc[reg] = block->firstDeadLoc[name];
+          inputVarsByReg[reg] = name;
+        }
+      }
+      for (auto pair : block->use) {
+        IString name = pair.first;
+        if (!inputVars.has(name)) {
+          inputVars.insert(name);
+          int reg = junctionVariables[name].reg;
+          assert(reg > 0); // 'input variable doesnt have a register');
+          inputDeadLoc[reg] = block->firstDeadLoc[name];
+          inputVarsByReg[reg] = name;
+        }
+      }
+      // TODO assert(setSize(setSub(inputVars, jEnter.live)) == 0);
+      // Scan through backwards, allocating registers on demand.
+      // Be careful to avoid conflicts with the input registers.
+      // We consume free registers in last-used order, which helps to
+      // eliminate "x=y" assignments that are the last use of "y".
+      StringIntMap assignedRegs;
+      auto freeRegsByTypePre = allRegsByType; // XXX copy
+      // Begin with all live vars assigned per the exit junction.
+      for (auto name : jExit.live) {
+        int reg = junctionVariables[name].reg;
+        assert(reg > 0); // 'output variable doesnt have a register');
+        assignedRegs[name] = reg;
+        freeRegsByTypePre[asmData.getType(name)].erase(reg); // XXX assert?
+      }
+      std::vector<std::vector<int>> freeRegsByType;
+      freeRegsByType.resize(freeRegsByTypePre.size());
+      for (int j = 0; j < freeRegsByTypePre.size(); j++) {
+        for (auto pair : freeRegsByTypePre[j]) {
+          freeRegsByType[j].push_back(pair.first);
+        }
+      }
+      // Scan through the nodes in sequence, modifying each node in-place
+      // and grabbing/freeing registers as needed.
+      std::vector<std::pair<int, Ref>> maybeRemoveNodes;
+      for (int j = block->nodes.size() - 1; j >= 0; j--) {
+        Ref node = block->nodes[j];
+        IString name = (node[0] == ASSIGN ? node[2][1] : node[1])->getIString();
+        IntStringMap& allRegs = allRegsByType[asmData.getType(name)];
+        std::vector<int>& freeRegs = freeRegsByType[asmData.getType(name)];
+        int reg = assignedRegs[name]; // XXX may insert a zero
+        if (node[0] == NAME) {
+          // A use.  Grab a register if it doesn't have one.
+          if (!reg) {
+            if (inputVars.has(name) && j <= block->firstDeadLoc[name]) {
+              // Assignment to an input variable, must use pre-assigned reg.
+              reg = junctionVariables[name].reg;
+              assignedRegs[name] = reg;
+              for (int k = freeRegs.size() - 1; k >= 0; k--) {
+                if (freeRegs[k] == reg) {
+                  freeRegs.erase(freeRegs.begin() + k);
+                  break;
+                }
+              }
+            } else {
+              // Try to use one of the existing free registers.
+              // It must not conflict with an input register.
+              for (int k = freeRegs.size() - 1; k >= 0; k--) {
+                reg = freeRegs[k];
+                // Check for conflict with input registers.
+                if (block->firstKillLoc[name] <= inputDeadLoc[reg]) {
+                  if (name != inputVarsByReg[reg]) {
+                    continue;
+                  }
+                }
+                // Found one!
+                assignedRegs[name] = reg;
+                freeRegs.erase(freeRegs.begin() + k);
+                break;
+              }
+              // If we didn't find a suitable register, create a new one.
+              if (!assignedRegs.has(name)) {
+                reg = createReg(name);
+                assignedRegs[name] = reg;
+              }
+            }
+          }
+          node[1]->setString(allRegs[reg]);
+        } else {
+          // A kill. This frees the assigned register.
+          assert(!!reg); //, 'live variable doesnt have a reg?')
+          node[2][1]->setString(allRegs[reg]);
+          freeRegs.push_back(reg);
+          assignedRegs.erase(name);
+          if (node[3][0] == NAME && asmData.isLocal(node[3][1]->getIString())) {
+            maybeRemoveNodes.push_back(std::pair<int, Ref>(j, node));
+          }
+        }
+      }
+      // If we managed to create an "x=x" assignments, remove them.
+      for (int j = 0; j < maybeRemoveNodes.size(); j++) {
+        Ref node = maybeRemoveNodes[j].second;
+        if (node[2][1] == node[3][1]) {
+          if (block->isexpr[maybeRemoveNodes[j].first]) {
+            safeCopy(node, node[2]);
+          } else {
+            safeCopy(node, makeBlock());
+          }
+        }
+      }
+    }
+
+    // Assign registers to function params based on entry junction
+
+    StringSet paramRegs;
+    if (!!fun[2]) {
+      for (int i = 0; i < fun[2]->size(); i++) {
+        auto& allRegs = allRegsByType[asmData.getType(fun[2][i]->getIString())];
+        fun[2][i]->setString(allRegs[junctionVariables[fun[2][i]->getIString()].reg]);
+        paramRegs.insert(fun[2][i]->getIString());
+      }
+    }
+
+    // That's it!
+    // Re-construct the function with appropriate variable definitions.
+
+    asmData.locals.clear();
+    asmData.params.clear();
+    asmData.vars.clear();
+    for (int i = 1; i < nextReg; i++) {
+      for (int type = 0; type < allRegsByType.size(); type++) {
+        if (allRegsByType[type].count(i) > 0) {
+          IString reg = allRegsByType[type][i];
+          if (!paramRegs.has(reg)) {
+            asmData.addVar(reg, intToAsmType(type));
+          } else {
+            asmData.addParam(reg, intToAsmType(type));
+          }
+          break;
+        }
+      }
+    }
+    asmData.denormalize();
+
+    removeAllUselessSubNodes(fun); // XXX vacuum?    vacuum(fun);
+  });
+}
+// end registerizeHarder
+
 // minified names generation
 StringSet RESERVED("do if in for new try var env let");
 const char *VALID_MIN_INITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
@@ -2738,6 +3845,7 @@ int main(int argc, char **argv) {
     else if (str == "optimizeFrounds") optimizeFrounds(doc);
     else if (str == "simplifyIfs") simplifyIfs(doc);
     else if (str == "registerize") registerize(doc);
+    //else if (str == "registerizeHarder") registerizeHarder(doc);
     else if (str == "minifyLocals") minifyLocals(doc);
     else if (str == "minifyWhitespace") {}
     else if (str == "asmLastOpts") asmLastOpts(doc);
diff --git a/tools/optimizer/parser.cpp b/tools/optimizer/parser.cpp
index 4a642a7103b5b..a650f4bd7bb1e 100644
--- a/tools/optimizer/parser.cpp
+++ b/tools/optimizer/parser.cpp
@@ -82,6 +82,7 @@ IString TOPLEVEL("toplevel"),
         NEW("new"),
         ARRAY("array"),
         OBJECT("object"),
+        THROW("throw"),
         SET("=");
 
 IStringSet keywords("var function if else do while for break continue return switch case default throw try catch finally true false null new"),
@@ -115,7 +116,7 @@ struct Init {
 
     precedences.resize(OperatorClass::Tertiary + 1);
 
-    for (int prec = 0; prec < operatorClasses.size(); prec++) {
+    for (size_t prec = 0; prec < operatorClasses.size(); prec++) {
       for (auto curr : operatorClasses[prec].ops) {
         precedences[operatorClasses[prec].type][curr] = prec;
       }
diff --git a/tools/optimizer/parser.h b/tools/optimizer/parser.h
index 1e822e908d6c4..16fc92df54e16 100644
--- a/tools/optimizer/parser.h
+++ b/tools/optimizer/parser.h
@@ -91,6 +91,7 @@ extern IString TOPLEVEL,
                NEW,
                ARRAY,
                OBJECT,
+               THROW,
                SET;
 
 extern IStringSet keywords, allOperators;
@@ -203,7 +204,7 @@ class Parser {
         type = NUMBER;
       } else if (hasChar(OPERATOR_INITS, *src)) {
         switch (*src) {
-          case '!': str = src[1] == '=' ? str = NE : str = L_NOT; break;
+          case '!': str = src[1] == '=' ? NE : L_NOT; break;
           case '%': str = MOD; break;
           case '&': str = AND; break;
           case '*': str = MUL; break;
diff --git a/tools/test-js-optimizer-asm-regs-harder-output.js b/tools/test-js-optimizer-asm-regs-harder-output.js
index c3b326f61feef..25e4e2db085df 100644
--- a/tools/test-js-optimizer-asm-regs-harder-output.js
+++ b/tools/test-js-optimizer-asm-regs-harder-output.js
@@ -116,7 +116,7 @@ function linkedVars() {
  while (1) {
   i2 = 9;
   i1 = 5;
-  while (i2 > 0 || i1 > 0) {
+  while (i2 > 0 | i1 > 0) {
    if (i2 < i1) {
     i2 = i2 - 1;
    } else {
diff --git a/tools/test-js-optimizer-asm-regs-harder.js b/tools/test-js-optimizer-asm-regs-harder.js
index fa72aab8135a2..faa2845920d62 100644
--- a/tools/test-js-optimizer-asm-regs-harder.js
+++ b/tools/test-js-optimizer-asm-regs-harder.js
@@ -108,7 +108,7 @@ function iffey() {
 }
 function labelledJump(x) {
  x = x | 0;
- var label = 0
+ var label = 0;
  // y and z don't conflict, but only if you know about labelled jumps.
  var y = 0, z = 0;
  y = 2;
@@ -129,7 +129,7 @@ function linkedVars() {
  while (1) {
   outer1 = 9;
   outer2 = 5;
-  while (outer1 > 0 || outer2 > 0) {
+  while ((outer1 > 0) | (outer2 > 0)) {
    // All these copy assignment should be eliminated by var sharing.
    inner1_0 = outer1;
    inner2_0 = outer2;